diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index 1cee72e6..00000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Bug report -about: Create a bug report to help us improve AdGuard Home ---- - -Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). - -### Prerequisites - -Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.** - -- [ ] I am running the latest version -- [ ] I checked the documentation and found no answer -- [ ] I checked to make sure that this issue has not already been filed - -### Issue Details - - - -* **Version of AdGuard Home server:** - * -* **How did you install AdGuard Home:** - * -* **How did you setup DNS configuration:** - * -* **If it's a router or IoT, please write device model:** - * -* **CPU architecture:** - * -* **Operating system and version:** - * - -### Expected Behavior - - -### Actual Behavior - - -### Screenshots - - -
Screenshot: - - - -
- -### Additional Information - diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index 094531b3..00000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: Feature request -about: Suggest a feature request for AdGuard Home ---- - -Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). - -### Prerequisites - -Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.** - -- [ ] I am running the latest version -- [ ] I checked the documentation and found no answer -- [ ] I checked to make sure that this issue has not already been filed - -### Problem Description - - -### Proposed Solution - - -### Alternatives Considered - - -### Additional Information - diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000..a5c40e4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,109 @@ +'body': + - 'attributes': + 'description': > + 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 post it to the GitHub + Discussions page. + 'label': 'Prerequisites' + 'options': + - 'label': > + I have checked the + [Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and + [Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions) + and found no answer + 'required': true + - 'label': > + I have searched other issues and found no duplicates + 'required': true + - 'label': > + I want to report a bug and not ask a question + 'required': true + 'id': 'prerequisites' + 'type': 'checkboxes' + - 'attributes': + 'description': 'On which operating system type does the issue occur?' + 'label': 'Operating system type' + 'options': + - 'FreeBSD' + - 'Linux, OpenWrt' + - 'Linux, Other (please mention the version in the description)' + - 'macOS (aka Darwin)' + - 'OpenBSD' + - 'Windows' + - 'Other (please mention in the description)' + 'id': 'os' + 'type': 'dropdown' + 'validations': + 'required': true + - 'attributes': + 'description': 'On which CPU architecture does the issue occur?' + 'label': 'CPU architecture' + 'options': + - 'AMD64' + - 'x86' + - '64-bit ARM' + - 'ARMv5' + - 'ARMv6' + - 'ARMv7' + - '64-bit MIPS' + - '64-bit MIPS LE' + - '32-bit MIPS' + - '32-bit MIPS LE' + - '64-bit PowerPC LE' + - 'Other (please mention in the description)' + 'id': 'arch' + 'type': 'dropdown' + 'validations': + 'required': true + - 'attributes': + 'description': 'How did you install AdGuard Home?' + 'label': 'Installation' + 'options': + - 'GitHub releases or script from README' + - 'Docker' + - 'Snapcraft' + - 'Custom port' + - 'Other (please mention in the description)' + 'id': 'install' + 'type': 'dropdown' + 'validations': + 'required': true + - 'attributes': + 'description': 'How did you setup AdGuard Home?' + 'label': 'Setup' + 'options': + - 'On one machine' + - 'On a router, DHCP is handled by the router' + - 'On a router, DHCP is handled by AdGuard Home' + - 'Other (please mention in the description)' + 'id': 'setup' + 'type': 'dropdown' + 'validations': + 'required': true + - 'attributes': + 'description': 'What version of AdGuard Home are you using?' + 'label': 'AdGuard Home version' + 'id': 'version' + 'type': 'input' + 'validations': + 'required': true + - 'attributes': + 'description': 'Please describe the bug' + 'label': 'Description' + 'value': | + #### What did you do? + + #### Expected result + + #### Actual result + + #### Screenshots (if applicable) + + #### Additional information + 'id': 'description' + 'type': 'textarea' + 'validations': + 'required': true +'description': 'File a bug report' +'name': 'Bug' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..921e07cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +'blank_issues_enabled': false +'contact_links': + - 'about': > + Please report filtering issues, for example advertising filters + misfiring or safe browsing false positives, using the form on our + website + 'name': 'AdGuard filters issues' + 'url': 'https://link.adtidy.org/forward.html?action=report&app=home&from=github' + - 'about': > + Please use GitHub Discussions for questions + 'name': 'Q&A Discussions' + 'url': 'https://github.com/AdguardTeam/AdGuardHome/discussions' + - 'about': > + Please check our Wiki for configuration file description, frequently + asked questions, and other documentation + 'name': 'Wiki' + 'url': 'https://github.com/AdguardTeam/AdGuardHome/wiki' diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 00000000..0ad6f5d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,41 @@ +'body': + - 'attributes': + 'description': > + 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 post it to the GitHub + Discussions page. + 'label': 'Prerequisites' + 'options': + - 'label': > + I have checked the + [Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and + [Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions) + and found no answer + 'required': true + - 'label': > + I have searched other issues and found no duplicates + 'required': true + - 'label': > + I want to request a feature or enhancement and not ask a + question + 'required': true + 'id': 'prerequisites' + 'type': 'checkboxes' + - 'attributes': + 'description': 'Please describe the request' + 'label': 'Description' + 'value': | + #### What problem are you trying to solve? + + #### Proposed solution + + #### Alternatives considered + + #### Additional information + 'id': 'description' + 'type': 'textarea' + 'validations': + 'required': true +'description': 'Suggest a feature or an enhancement for AdGuard Home' +'name': 'Feature request or enhancement' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af2dd565..61ed05e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ 'name': 'build' 'env': - 'GO_VERSION': '1.17' + 'GO_VERSION': '1.18' 'NODE_VERSION': '14' 'on': diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c1faeaa9..1842c2dc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ 'name': 'lint' 'env': - 'GO_VERSION': '1.17' + 'GO_VERSION': '1.18' 'on': 'push': diff --git a/.twosky.json b/.twosky.json index 244df6a9..e7721ca6 100644 --- a/.twosky.json +++ b/.twosky.json @@ -2,8 +2,11 @@ { "project_id": "home", "base_locale": "en", - "localizable_files": ["client/src/__locales/en.json"], + "localizable_files": [ + "client/src/__locales/en.json" + ], "languages": { + "ar": "العربية", "be": "Беларуская", "bg": "Български", "cs": "Český", diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f45312..b5edd91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,19 +12,192 @@ and this project adheres to ## [Unreleased] ### Security -- Enforced password strength policy ([#3503]). - Weaker cipher suites that use the CBC (cipher block chaining) mode of operation have been disabled ([#2993]). +### Deprecated + +- Ports 784 and 8853 for DNS-over-QUIC in Docker images. Users who still serve + DoQ on these ports are encouraged to move to the standard port 853. These + ports will be removed from the `EXPOSE` section of our `Dockerfile` in a + future release. +- Go 1.18 support. v0.109.0 will require at least Go 1.19 to build. + +### Fixed + +- Dynamic leases created with empty hostnames ([#4745]). +- Unnecessary logging of non-critical statistics errors ([#4850]). + +[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993 +[#4745]: https://github.com/AdguardTeam/AdGuardHome/issues/4745 +[#4850]: https://github.com/AdguardTeam/AdGuardHome/issues/4850 + + + + + + + +## [v0.107.11] - 2022-08-19 + +See also the [v0.107.11 GitHub milestone][ms-v0.107.11]. + ### Added -- Support for Discovery of Designated Resolvers (DDR) according to the - [RFC draft][ddr-draft-06] ([#4463]). +- Bilibili service blocking ([#4795]). + +### Changed + +- DNS-over-QUIC connections now use keptalive. + +### Fixed + +- Migrations from releases older than v0.107.7 failing ([#4846]). + +[#4795]: https://github.com/AdguardTeam/AdGuardHome/issues/4795 +[#4846]: https://github.com/AdguardTeam/AdGuardHome/issues/4846 + +[ms-v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/milestone/47?closed=1 + + + +## [v0.107.10] - 2022-08-17 + +See also the [v0.107.10 GitHub milestone][ms-v0.107.10]. + +### Added + +- Arabic localization. +- Support for Discovery of Designated Resolvers (DDR) according to the [RFC + draft][ddr-draft] ([#4463]). + +### Changed + +- Our snap package now uses the `core22` image as its base ([#4843]). + +### Fixed + +- DHCP not working on most OSes ([#4836]). +- `invalid argument` errors during update checks on older Linux kernels + ([#4670]). +- Data races and concurrent map access in statistics module ([#4358], [#4342]). + +[#4342]: https://github.com/AdguardTeam/AdGuardHome/issues/4342 +[#4358]: https://github.com/AdguardTeam/AdGuardHome/issues/4358 +[#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670 +[#4836]: https://github.com/AdguardTeam/AdGuardHome/issues/4836 +[#4843]: https://github.com/AdguardTeam/AdGuardHome/issues/4843 + +[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08 +[ms-v0.107.10]: https://github.com/AdguardTeam/AdGuardHome/milestone/46?closed=1 + + + +## [v0.107.9] - 2022-08-03 + +See also the [v0.107.9 GitHub milestone][ms-v0.107.9]. + +### Security + +- Go version was updated to prevent the possibility of exploiting the + CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5]. Go 1.17 + support has also been removed, as it has reached end of life and will not + receive security updates. + +### Added + +- Domain-specific upstream servers test. If such test fails, a warning message + is shown ([#4517]). +- `windows/arm64` support ([#3057]). + +### Changed + +- UI and update links have been changed to make them more resistant to DNS + blocking. + +### Fixed + +- DHCP not working on most OSes ([#4836]). +- Several UI issues ([#4775], [#4776], [#4782]). + +### Removed + +- Go 1.17 support, as it has reached end of life. + +[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057 +[#4517]: https://github.com/AdguardTeam/AdGuardHome/issues/4517 +[#4775]: https://github.com/AdguardTeam/AdGuardHome/issues/4775 +[#4776]: https://github.com/AdguardTeam/AdGuardHome/issues/4776 +[#4782]: https://github.com/AdguardTeam/AdGuardHome/issues/4782 +[#4836]: https://github.com/AdguardTeam/AdGuardHome/issues/4836 + +[go-1.18.5]: https://groups.google.com/g/golang-announce/c/YqYYG87xB10 +[ms-v0.107.9]: https://github.com/AdguardTeam/AdGuardHome/milestone/45?closed=1 + + + +## [v0.107.8] - 2022-07-13 + +See also the [v0.107.8 GitHub milestone][ms-v0.107.8]. + +### Security + +- Go version was updated to prevent the possibility of exploiting the + CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities + fixed in [Go 1.17.12][go-1.17.12]. + + + +### Fixed + +- DHCP lease validation incorrectly letting users assign the IP address of the + gateway as the address of the lease ([#4698]). +- Updater no longer expects a hardcoded name for `AdGuardHome` executable + ([#4219]). +- Inconsistent names of runtime clients from hosts files ([#4683]). +- PTR requests for addresses leased by DHCP will now be resolved into hostnames + under `dhcp.local_domain_name` ([#4699]). +- Broken service installation on OpenWrt ([#4677]). + +[#4219]: https://github.com/AdguardTeam/AdGuardHome/issues/4219 +[#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677 +[#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683 +[#4698]: https://github.com/AdguardTeam/AdGuardHome/issues/4698 +[#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699 + +[go-1.17.12]: https://groups.google.com/g/golang-announce/c/nqrv9fbR0zE +[ms-v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/milestone/44?closed=1 + + + +## [v0.107.7] - 2022-06-06 + +See also the [v0.107.7 GitHub milestone][ms-v0.107.7]. + +### Security + +- Go version was updated to prevent the possibility of exploiting the + [CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and + [CVE-2022-29804] Go vulnerabilities. +- Enforced password strength policy ([#3503]). + +### Added + +- Support for the final DNS-over-QUIC standard, [RFC 9250][rfc-9250] ([#4592]). +- Support upstreams for subdomains of a domain only ([#4503]). - The ability to control each source of runtime clients separately via `clients.runtime_sources` configuration object ([#3020]). - The ability to customize the set of networks that are considered private @@ -36,12 +209,14 @@ and this project adheres to ([#4166]). - Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is installed as a service ([#4213]). -- `windows/arm64` support ([#3057]). ### Changed +- On OpenBSD, the daemon script now uses the recommended `/bin/ksh` shell + instead of the `/bin/sh` one ([#4533]). To apply this change, backup your + data and run `AdGuardHome -s uninstall && AdGuardHome -s install`. - The default DNS-over-QUIC port number is now `853` instead of `754` in - accordance with the latest [RFC draft][doq-draft-10] ([#4276]). + accordance with [RFC 9250][rfc-9250] ([#4276]). - Reverse DNS now has a greater priority as the source of runtime clients' information than ARP neighborhood. - Improved detection of runtime clients through more resilient ARP processing @@ -96,7 +271,7 @@ In this release, the schema version has changed from 12 to 14. `dns.resolve_clients` property. To rollback this change, remove the `runtime_sources` property, move the contents of `persistent` into the `clients` itself, the value of `clients.runtime_sources.rdns` into the - `dns.resolve_clietns`, and change the `schema_version` back to `13`. + `dns.resolve_clients`, and change the `schema_version` back to `13`. - Property `local_domain_name`, which in schema versions 12 and earlier used to be a part of the `dns` object, is now a part of the `dhcp` object: @@ -117,20 +292,21 @@ In this release, the schema version has changed from 12 to 14. ### Deprecated -- The `--no-etc-hosts` option. Its' functionality is now controlled by +- The `--no-etc-hosts` option. Its functionality is now controlled by `clients.runtime_sources.hosts` configuration property. v0.109.0 will remove the flag completely. -- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build. ### Fixed -- Slow version update queries making other HTTP APIs unresponsible ([#4499]). +- Query log occasionally going into an infinite loop ([#4591]). +- Service startup on boot on systems using SysV-init ([#4480]). +- Detection of the stopped service status on macOS and Linux ([#4273]). +- Case-sensitive ClientID ([#4542]). +- Slow version update queries making other HTTP APIs unresponsive ([#4499]). - ARP tables refreshing process causing excessive PTR requests ([#3157]). [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730 -[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993 [#3020]: https://github.com/AdguardTeam/AdGuardHome/issues/3020 -[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057 [#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142 [#3157]: https://github.com/AdguardTeam/AdGuardHome/issues/3157 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367 @@ -142,22 +318,23 @@ In this release, the schema version has changed from 12 to 14. [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238 +[#4273]: https://github.com/AdguardTeam/AdGuardHome/issues/4273 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276 +[#4480]: https://github.com/AdguardTeam/AdGuardHome/issues/4480 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499 +[#4503]: https://github.com/AdguardTeam/AdGuardHome/issues/4503 +[#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533 +[#4542]: https://github.com/AdguardTeam/AdGuardHome/issues/4542 +[#4591]: https://github.com/AdguardTeam/AdGuardHome/issues/4591 +[#4592]: https://github.com/AdguardTeam/AdGuardHome/issues/4592 -[ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html -[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2 -[repr]: https://reproducible-builds.org/docs/source-date-epoch/ - - - - +[CVE-2022-29526]: https://www.cvedetails.com/cve/CVE-2022-29526 +[CVE-2022-29804]: https://www.cvedetails.com/cve/CVE-2022-29804 +[CVE-2022-30580]: https://www.cvedetails.com/cve/CVE-2022-30580 +[CVE-2022-30629]: https://www.cvedetails.com/cve/CVE-2022-30629 +[CVE-2022-30634]: https://www.cvedetails.com/cve/CVE-2022-30634 +[ms-v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/milestone/43?closed=1 +[rfc-9250]: https://datatracker.ietf.org/doc/html/rfc9250 @@ -169,7 +346,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6]. - `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests. - Go version was updated to prevent the possibility of exploiting the - [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] vulnerabilities. + [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities. ### Added @@ -211,6 +388,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6]. [CVE-2022-28327]: https://www.cvedetails.com/cve/CVE-2022-28327 [dns-draft-02]: https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1 [ms-v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1 +[repr]: https://reproducible-builds.org/docs/source-date-epoch/ [svcb-draft-08]: https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html @@ -223,7 +401,7 @@ were resolved. ### Security - Go version was updated to prevent the possibility of exploiting the - [CVE-2022-24921] vulnerability. + [CVE-2022-24921] Go vulnerability. [CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921 @@ -236,7 +414,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4]. ### Security - Go version was updated to prevent the possibility of exploiting the - [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities. + [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities. ### Fixed @@ -337,7 +515,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0]. - Upstream server information for responses from cache ([#3772]). Note that old log entries concerning cached responses won't include that information. -- Finnish and Ukrainian translations. +- Finnish and Ukrainian localizations. - Setting the timeout for IP address pinging in the "Fastest IP address" mode through the new `fastest_timeout` field in the configuration file ([#1992]). - Static IP address detection on FreeBSD ([#3289]). @@ -972,11 +1150,16 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2]. -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD +[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...HEAD +[v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11 +[v0.107.10]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10 +[v0.107.9]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9 +[v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8 +[v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7 [v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6 [v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5 [v0.107.4]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4 diff --git a/Makefile b/Makefile index 7c8f205f..b4823bb7 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ V1API = 0 # into BUILD_RELEASE_DEPS_0, and so both frontend and backend # dependencies are fetched and the frontend is built. Otherwise, if # FRONTEND_PREBUILT is 1, only backend dependencies are fetched and the -# frontend isn't reuilt. +# frontend isn't rebuilt. # # TODO(a.garipov): We could probably do that from .../build-release.sh, # but that would mean either calling make from inside make or diff --git a/README.md b/README.md index 00664365..2118b116 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@  

- AdGuard Home + + + AdGuard Home +

Privacy protection center for you and your devices

- Free and open source, powerful network-wide ads & trackers blocking DNS server. + Free and open source, powerful network-wide ads & trackers blocking DNS + server.

@@ -23,9 +27,6 @@ Docker Pulls - - Docker Stars -
Latest release @@ -38,14 +39,19 @@

- +


AdGuard Home is a network-wide software for blocking ads & 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](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common 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](https://adguard-dns.io/) servers, +and both share a lot of code. + + * [Getting Started](#getting-started) * [Comparing AdGuard Home to other solutions](#comparison) @@ -66,8 +72,10 @@ It operates as a DNS server that re-routes tracking domains to a "black hole", t ## Getting Started ### Automated install (Linux and Mac) + Run the following command in your terminal: -``` + +```sh curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v ``` @@ -114,7 +122,7 @@ If you're running **Linux**, there's a secure and easy way to install AdGuard Ho ### API If you want to integrate with AdGuard Home, you can use our [REST API](https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi). -Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://community.home-assistant.io/t/community-hass-io-add-on-adguard-home). +Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://www.home-assistant.io/integrations/adguard/).
## Comparing AdGuard Home to other solutions @@ -161,7 +169,15 @@ AdGuard Home provides a lot of features out-of-the-box with no need to install a It depends. -"DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It 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 +flexibility and power of traditional ad blockers. You can get a good impression +about the difference between these methods by reading +[this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It +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). @@ -185,7 +201,7 @@ Run `make init` to prepare the development environment. You will need this to build AdGuard Home: - * [go](https://golang.org/dl/) v1.17 or later. + * [go](https://golang.org/dl/) v1.18 or later. * [node.js](https://nodejs.org/en/download/) v10.16.2 or later. * [npm](https://www.npmjs.com/) v6.14 or later (temporary requirement, TODO: remove when redesign is finished). * [yarn](https://yarnpkg.com/) v1.22.5 or later. @@ -194,7 +210,7 @@ You will need this to build AdGuard Home: Open Terminal and execute these commands: -```bash +```sh git clone https://github.com/AdguardTeam/AdGuardHome cd AdGuardHome make @@ -212,11 +228,14 @@ Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Ma In order to do this, specify `GOOS` and `GOARCH` env variables before running make. For example: -``` + +```sh env GOOS='linux' GOARCH='arm64' make ``` -Or: -``` + +or: + +```sh make GOOS='linux' GOARCH='arm64' ``` @@ -228,7 +247,7 @@ You'll need this to prepare a release build: Commands: -``` +```sh make build-release CHANNEL='...' VERSION='...' ``` @@ -271,38 +290,40 @@ There are three options how you can install an unstable version: 3. Standalone builds. Use the automated installation script or look for the available builds below. Beta: -``` + +```sh curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c beta ``` Edge: -``` + +```sh curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c edge ``` * Beta channel builds - * Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz) - * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz) - * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz) - * Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip) - * macOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip) - * macOS ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_arm64.zip) - * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz) - * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz) + * Linux: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz) + * Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz) + * Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz) + * Windows: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip) + * macOS: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip) + * macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_arm64.zip) + * FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz) + * FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz) * OpenBSD: (coming soon) * OpenBSD ARM: (coming soon) * Edge channel builds - * Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz) - * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz) - * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz) - * Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip) - * macOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip) - * macOS ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_arm64.zip) - * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz) - * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz) - * OpenBSD: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz) - * OpenBSD ARM: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz) + * Linux: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_386.tar.gz) + * Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz) + * Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz) + * Windows: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_386.zip) + * macOS: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_386.zip) + * macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_arm64.zip) + * FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz) + * FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz) + * OpenBSD: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz) + * OpenBSD ARM: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz) @@ -313,9 +334,12 @@ If you run into any problem or have a suggestion, head to [this page](https://gi ### Help with translations -If you want to help with AdGuard Home translations, please learn more about translating AdGuard products here: https://kb.adguard.com/en/general/adguard-translations +If you want to help with AdGuard Home translations, please learn more about +translating AdGuard products +[in our Knowledge Base](https://kb.adguard.com/en/general/adguard-translations). -Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-applications/en#/adguard-home +Here is a link to AdGuard Home project: + ### Other @@ -345,6 +369,7 @@ Here's what you can also do to contribute: * [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by [@ebrianne](https://github.com/ebrianne) * [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/) * [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by [@gramakri](https://github.com/gramakri) +* [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/) ## Acknowledgments @@ -364,11 +389,17 @@ This software wouldn't have been possible without: * And many more node.js packages. * [whotracks.me data](https://github.com/cliqz-oss/whotracks.me) -You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuard Home. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded Home features that we plan to implement. +You might have seen that [CoreDNS](https://coredns.io) was mentioned here +before, but we've stopped using it in AdGuard Home. For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file. ## Privacy -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. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html). + +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. Full policy +with every bit that *could in theory be* sent by AdGuard Home is available +[here](https://adguard.com/en/privacy/home.html) diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml index d0513f0f..e379dc73 100644 --- a/bamboo-specs/release.yaml +++ b/bamboo-specs/release.yaml @@ -7,9 +7,15 @@ # Make sure to sync any changes with the branch overrides below. 'variables': 'channel': 'edge' - 'dockerGo': 'adguard/golang-ubuntu:4.3' + 'dockerGo': 'adguard/golang-ubuntu:5.0' 'stages': +- 'Build frontend': + 'manual': false + 'final': false + 'jobs': + - 'Build frontend' + - 'Make release': 'manual': false 'final': false @@ -22,11 +28,11 @@ 'jobs': - 'Make and publish docker' -- 'Publish to static.adguard.com': +- 'Publish to static storage': 'manual': false 'final': false 'jobs': - - 'Publish to static.adguard.com' + - 'Publish to static storage' - 'Publish to Snapstore': 'manual': false @@ -40,11 +46,41 @@ 'jobs': - 'Publish to GitHub Releases' -'Make release': +'Build frontend': 'docker': 'image': '${bamboo.dockerGo}' '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 + + # 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 + 'requirements': + - 'adg-docker': 'true' + +'Make release': + 'docker': + 'image': '${bamboo.dockerGo}' + 'volumes': '${system.GO_CACHE_DIR}': '${bamboo.cacheGo}' '${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}' 'key': 'MR' @@ -65,13 +101,14 @@ git checkout "${bamboo.repository.revision.number}" # Run the build with the specified channel. - echo "${bamboo.gpgSecretKey}"\ + echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\ | awk '{ gsub(/\\n/, "\n"); print; }'\ | gpg --import --batch --yes make\ CHANNEL=${bamboo.channel}\ GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\ + FRONTEND_PREBUILT=1\ VERBOSE=1\ build-release # TODO(a.garipov): Use more fine-grained artifact rules. @@ -132,7 +169,7 @@ 'requirements': - 'adg-docker': 'true' -'Publish to static.adguard.com': +'Publish to static storage': 'key': 'PUB' 'other': 'clean-working-dir': true @@ -285,7 +322,7 @@ # need to build a few of these. 'variables': 'channel': 'beta' - 'dockerGo': 'adguard/golang-ubuntu:4.3' + 'dockerGo': 'adguard/golang-ubuntu:5.0' # release-vX.Y.Z branches are the branches from which the actual final release # is built. - '^release-v[0-9]+\.[0-9]+\.[0-9]+': @@ -300,4 +337,4 @@ # are the ones that actually get released. 'variables': 'channel': 'release' - 'dockerGo': 'adguard/golang-ubuntu:4.3' + 'dockerGo': 'adguard/golang-ubuntu:5.0' diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml index e973b77c..cd799fda 100644 --- a/bamboo-specs/test.yaml +++ b/bamboo-specs/test.yaml @@ -5,7 +5,7 @@ 'key': 'AHBRTSPECS' 'name': 'AdGuard Home - Build and run tests' 'variables': - 'dockerGo': 'adguard/golang-ubuntu:4.3' + 'dockerGo': 'adguard/golang-ubuntu:5.0' 'stages': - 'Tests': diff --git a/client/src/__locales/ar.json b/client/src/__locales/ar.json new file mode 100644 index 00000000..2384ecaf --- /dev/null +++ b/client/src/__locales/ar.json @@ -0,0 +1,634 @@ +{ + "client_settings": "الإعدادات", + "example_upstream_reserved": "يمكنك تحديد <0> DNS upstream لنطاق معين (نطاقات) ", + "example_upstream_comment": "يمكنك تحديد تعليق", + "upstream_parallel": "استخدام الاستعلامات المتوازية لتسريع الحل عن طريق الاستعلام في وقت واحد عن جميع خوادم المنبع", + "parallel_requests": "طلبات موازية", + "load_balancing": "توزيع الحمل", + "load_balancing_desc": "الاستعلام عن خادم واحد في كل مرة سيستخدم AdGuard الرئيسية الخوارزمية العشوائية الموزونة لاختيار الخادم بحيث يتم استخدام أسرع خادم في كثير من الأحيان", + "bootstrap_dns": "خوادم Bootstrap DNS", + "bootstrap_dns_desc": "يتم استخدام خوادم Bootstrap DNS لحل عناوين IP الخاصة بمحللات DoH / DoT التي تحددها على هيئة تدفقات.", + "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": "أدخل عنوان خادم واحد لكل سطر", + "resolve_clients_title": "تفعيل التحليل العكسي لعناوين IP للعملاء", + "resolve_clients_desc": "حل عكسيًا لعناوين IP للعملاء في أسماء مضيفيهم عن طريق إرسال استعلامات PTR إلى أدوات الحل المقابلة (خوادم DNS الخاصة للعملاء المحليين ، والخوادم الأولية للعملاء الذين لديهم عناوين IP عامة).", + "use_private_ptr_resolvers_title": "استخدم محللات DNS العكسية الخاصة", + "use_private_ptr_resolvers_desc": "قم بإجراء عمليات بحث DNS عكسية عن العناوين التي يتم تقديمها محليًا باستخدام هذه الخوادم الأولية. في حالة التعطيل ، يستجيب AdGuard Home مع NXDOMAIN لجميع طلبات PTR هذه باستثناء العملاء المعروفين من DHCP و / etc / hosts وما إلى ذلك.", + "check_dhcp_servers": "تحقق من خوادم DHCP", + "save_config": "حفظ الإعدادات", + "enabled_dhcp": "خادم DHCP مفعل", + "disabled_dhcp": "خادم DHCP غير مفعل", + "unavailable_dhcp": "DHCP غير متوفر", + "unavailable_dhcp_desc": "لا يمكن لـ AdGuard Home تشغيل خادم DHCP على نظام التشغيل الخاص بك", + "dhcp_title": "خادم DHCP (تجريبي!)", + "dhcp_description": "إذا كان جهاز الراوتر الخاص بك لا يوفر إعدادات DHCP ، يمكنك استخدام خادم DHCP المدمج في AdGuard.", + "dhcp_enable": "فعل خادم DHCP", + "dhcp_disable": "عطل خادم DHCP", + "dhcp_not_found": "من الآمن تمكين خادم DHCP المدمج - لم نعثر على أي خوادم DHCP نشطة على الشبكة. ومع ذلك ، نشجعك على إعادة التحقق يدويًا لأن اختبارنا التلقائي في الوقت الحالي لا يوفر ضمانًا بنسبة 100٪.", + "dhcp_found": "تم العثور على خادم DHCP نشط على الشبكة. وبالتالي لا ينصح بتفعيل خادم DHCP المدمج.", + "dhcp_leases": "عقود إيجار DHCP", + "dhcp_static_leases": "إيجارات DHCP الثابتة", + "dhcp_leases_not_found": "لم يتم العثور على عقود إيجار DHCP", + "dhcp_config_saved": "الإعدادات محفوظة لخادم DHCP", + "dhcp_ipv4_settings": "DHCP IPv4 إعدادات", + "dhcp_ipv6_settings": "DHCP IPv6 إعدادات", + "form_error_required": "الحقل مطلوب", + "form_error_ip4_format": "عنوان IPv4 غير صالح", + "form_error_ip4_range_start_format": "عناوين البداية لـIPv4 غير صالحة للنطاق", + "form_error_ip4_range_end_format": "عناوين IPv4 غير صالحة لنطاق النهاية", + "form_error_ip4_gateway_format": "عنوان IPv4 غير صالح للبوابة", + "form_error_ip6_format": "عنوان IPv6 غير صالح", + "form_error_ip_format": "عنوان IP غير صحيح", + "form_error_mac_format": "عنوان MAC غير صالح", + "form_error_client_id_format": "يجب أن يحتوي معرف العميل على الأرقام والأحرف الصغيرة والواصلات فقط", + "form_error_server_name": "اسم الخادم غير صالح", + "form_error_subnet": "لا تحتوي الشبكة الفرعية \"{{cidr}}\" على عنوان IP \"{{ip}}\"", + "form_error_positive": "يجب أن يكون أكبر من 0", + "out_of_range_error": "يجب أن يكون خارج النطاق \"{{start}}\" - \"{{end}}\"", + "lower_range_start_error": "يجب أن يكون أقل من نطاق البداية", + "greater_range_start_error": "يجب أن يكون أكبر من نطاق البداية", + "greater_range_end_error": "يجب أن يكون أكبر من نطاق النهاية", + "subnet_error": "يجب أن تكون العناوين في شبكة فرعية واحدة", + "gateway_or_subnet_invalid": "قناع الشبكة الفرعية غير صالح", + "dhcp_form_gateway_input": "IP البوابة", + "dhcp_form_subnet_input": "قناع الشبكة الفرعية", + "dhcp_form_range_title": "مجموعة عناوين IP", + "dhcp_form_range_start": "نطاق البداية", + "dhcp_form_range_end": "نطاق النهاية", + "dhcp_form_lease_title": "مدة تأجير DHCP (بالثواني)", + "dhcp_form_lease_input": "مدة الإيجار", + "dhcp_interface_select": "حدد واجهة DHCP", + "dhcp_hardware_address": "عناوين الأجهزة", + "dhcp_ip_addresses": "عناوين الـIP", + "ip": "IP", + "dhcp_table_hostname": "اسم المضيف", + "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}}. من أجل استعمال خادم DHCP ، يجب تعيين عنوان IP ثابت. عنوان IP الحالي الخاص بك هو <0>{{ipAddress}}. إذا ضغطت على زر تفعيل DHCP سنقوم تلقائيًا بتعيين عنوان الIP هذا على أنه ثابت.", + "dhcp_lease_added": "تمت أضافة مدة الايجار \"{{key}}\" بنجاح", + "dhcp_lease_deleted": "تمت ازالة مدة الايجار \"{{key}}\" بنجاح", + "dhcp_new_static_lease": "عقد إيجار ثابت جديد", + "dhcp_static_leases_not_found": "لم يتم العثور على عقود إيجار ثابتة DHCP", + "dhcp_add_static_lease": "إضافة عقد إيجار ثابت", + "dhcp_reset_leases": "إعادة تعيين كافة عقود الإيجار", + "dhcp_reset_leases_confirm": "هل أنت متأكد أنك تريد إعادة تعيين كافة عقود الإيجار؟", + "dhcp_reset_leases_success": "إعادة تعيين تأجير DHCP بنجاح", + "dhcp_reset": "هل أنت متأكد من أنك تريد إعادة تعيين تكوين DHCP؟", + "country": "الدولة", + "city": "المدينة", + "delete_confirm": "هل أنت متأكد من أنك تريد حذف \"{{key}}\"؟", + "form_enter_hostname": "أدخل اسم الhostname", + "error_details": "مزيد من التفاصيل حول الخطأ", + "response_details": "تفاصيل الاستجابة", + "request_details": "تفاصيل الطلب", + "client_details": "تفاصيل العميل", + "details": "التفاصيل", + "back": "رجوع", + "dashboard": "لوحة القيادة", + "settings": "الإعدادات", + "filters": "الفلاتر", + "filter": "فلتر", + "query_log": "سجل الQuery", + "compact": "المدمج", + "nothing_found": "لم يتم العثور علي شيء...", + "faq": "أسئلة مكررة", + "version": "الإصدار", + "address": "العناوين", + "protocol": "البروتوكول", + "on": "ON", + "off": "OFF", + "copyright": "حقوق النشر", + "homepage": "الصفحة الرئيسية", + "report_an_issue": "الإبلاغ عن مشكلة", + "privacy_policy": "سياسة الخصوصية", + "enable_protection": "تفعيل الحماية", + "enabled_protection": "الحماية مفعلة", + "disable_protection": "تعطيل الحماية", + "disabled_protection": "الحماية غير مفعلة", + "refresh_statics": "تحيين الإحصائيات", + "dns_query": "DNS Queries", + "blocked_by": "<0>تم حظره بواسطة الفلاتر", + "stats_malware_phishing": "حسر البرامج الضارة / والتصيّد", + "stats_adult": "حظر مواقع الويب الخاصة بالبالغين", + "stats_query_domain": "اعلى النطاقات التي تم الاستعلام عنها", + "for_last_24_hours": "لأخر 24 ساعة", + "for_last_days": "لآخر {{value}} يوم", + "for_last_days_plural": "لآخر {{count}} ايام", + "stats_disabled": "تم تعطيل الإحصائيات. يمكنك تشغيله من <0> صفحة الإعدادات .", + "stats_disabled_short": "تم تعطيل الإحصائيات", + "no_domains_found": "لم يتم العثور على النطاق", + "requests_count": "عدد الطلبات", + "top_blocked_domains": "اعلى الدومينات المحظورة", + "top_clients": "كبار العملاء", + "no_clients_found": "لم يتم العثور على عملاء", + "general_statistics": "الإحصاءات العامة", + "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_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_processing_time_hint": "متوسط الوقت بالمللي ثانية عند معالجة طلب DNS", + "block_domain_use_filters_and_hosts": "حظر النطاقات باستخدام عوامل التصفية وملفات المضيفين", + "filters_block_toggle_hint": "يمكنك إعداد قواعد حظر في المرشحات اعدادات.", + "use_adguard_browsing_sec": "استخدم خدمة الويب الأمنية لتصفح AdGuard", + "use_adguard_browsing_sec_hint": "سيتحقق AdGuard Home مما إذا كان النطاق محظورًا بواسطة خدمة الويب الخاصة بأمان التصفح. سيستخدم واجهة برمجة تطبيقات بحث صديقة للخصوصية لإجراء الفحص: يتم إرسال بادئة قصيرة فقط من تجزئة اسم المجال SHA256 إلى الخادم.", + "use_adguard_parental": "استخدام خدمة AdGuard للرقابة الأبوية على الويب", + "use_adguard_parental_hint": "سيتحقق AdGuard Home مما إذا كان النطاق يحتوي على محتوى للبالغين. إنه يستخدم نفس واجهة برمجة التطبيقات الصديقة للخصوصية مثل خدمة الويب الأمنية للتصفح.", + "enforce_safe_search": "استخدم البحث الآمن", + "enforce_save_search_hint": "سيفرض AdGuard Home البحث الآمن في محركات البحث التالية: Google وYouTube وBing وDuckDuckGo وYandex وPixabay.", + "no_servers_specified": "لم يتم تحديد خوادم", + "general_settings": "الإعدادات العامة", + "dns_settings": "إعدادات الـ DNS", + "dns_blocklists": "قوائم حظر DNS", + "dns_allowlists": "قوائم السماح لـ DNS", + "dns_blocklists_desc": "سيقوم AdGuard Home بحظر النطاقات المطابقة لقوائم الحظر", + "dns_allowlists_desc": "سيتم السماح بالنطاقات من قوائم DNS المسموحة حتى لو كانت في أي من قوائم الحظر", + "custom_filtering_rules": "قواعد التصفية المخصصة", + "encryption_settings": "إعدادات التعمية", + "dhcp_settings": "إعدادات DHCP", + "upstream_dns": "خادم DNS لـ Upstream", + "upstream_dns_help": "أدخل عنوان خادم واحد في كل سطر. تعرف على المزيد حول تكوين خوادم DNS الأولية.", + "upstream_dns_configured_in_file": "تم اعداده في {{path}}", + "test_upstream_btn": "اختبار upstream", + "upstreams": "Upstreams", + "apply_btn": "تطبيق", + "disabled_filtering_toast": "تم تعطيل الفلترة", + "enabled_filtering_toast": "تم تمكين الفلترة", + "disabled_safe_browsing_toast": "تم تعطيل التصفح الآمن", + "enabled_safe_browsing_toast": "تم تمكين التصفح الآمن", + "disabled_parental_toast": "تعطيل الرقابة الأبوية", + "enabled_parental_toast": "تفعيل الرقابة الأبوية", + "disabled_safe_search_toast": "تعطيل البحث الآمن", + "enabled_save_search_toast": "تفعيل البحث الآمن", + "enabled_table_header": "تمكين", + "name_table_header": "الاسم", + "list_url_table_header": "قائمة الروابط", + "rules_count_table_header": "عدد القواعد", + "last_time_updated_table_header": "آخر تحديث", + "actions_table_header": "الإجراءات", + "request_table_header": "طلب", + "edit_table_action": "تحرير", + "delete_table_action": "حذف", + "elapsed": "المنقضي", + "filters_and_hosts_hint": "يفهم AdGuard Home قواعد حظر الإعلانات الاساسية وملفات الهوست.", + "no_blocklist_added": "لم يتم إضافة قوائم الحظر", + "no_whitelist_added": "لم تتم إضافة قوائم السماح", + "add_blocklist": "إضافة قائمة الحظر", + "add_allowlist": "إضافة قائمة السماح", + "cancel_btn": "إلغاء", + "enter_name_hint": "أدخل الاسم", + "enter_url_or_path_hint": "إدخال عنوان URL أو مسار مطلق للقائمة", + "check_updates_btn": "تحقق من وجود تحديثات", + "new_blocklist": "قائمة حظر جديدة", + "new_allowlist": "قائمة السماح الجديدة", + "edit_blocklist": "تحرير قائمة الحظر", + "edit_allowlist": "تحرير قائمة السماح", + "choose_blocklist": "اختر قوائم الحظر", + "choose_allowlist": "اختر قوائم السماح", + "enter_valid_blocklist": "إدخال عنوان URL صالح إلى قائمة الحظر", + "enter_valid_allowlist": "أدخل عنوان URL صالحًا لقائمة السماح", + "form_error_url_format": "تنسيق رابط غير صالح", + "form_error_url_or_path_format": "عنوان URL أو المسار المطلق للقائمة غير صالح", + "custom_filter_rules": "قواعد التصفية المخصصة", + "custom_filter_rules_hint": "أدخل قاعدة واحدة على السطر يمكنك استخدام قواعد adblock أو بناء جملة ملفات المضيفين", + "system_host_files": "ملفات الهوست للنظام", + "examples_title": "أمثلة", + "example_meaning_filter_block": "منع الوصول إلى نطاق example.org وجميع نطاقاته الفرعية", + "example_meaning_filter_whitelist": "إلغاء حظر الوصول إلى نطاق example.org وجميع نطاقاته الفرعية", + "example_meaning_host_block": "الرد ب 127.0.0.1 على example.org (ولكن ليس لنطاقاته الفرعية);", + "example_comment": "! ها هو التعليق.", + "example_comment_meaning": "فقط تعليق;", + "example_comment_hash": "# تعليق أيضًا", + "example_regex_meaning": "منع الوصول إلى النطاقات المطابقة للتعبير العادي المحدد.", + "example_upstream_regular": "regular DNS (over UDP);", + "example_upstream_udp": "regular DNS (over UDP, hostname);", + "example_upstream_dot": "مشفر<0>DNS-over-TLS;", + "example_upstream_doh": "مشفر <0>DNS-over-HTTPS;", + "example_upstream_doq": "encrypted <0>DNS-over-QUIC;", + "example_upstream_sdns": "<0>DNS Stamps for <1>DNSCrypt or <2>DNS-over-HTTPS resolvers;", + "example_upstream_tcp": "regular DNS (over TCP);", + "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);", + "all_lists_up_to_date_toast": "جميع القوائم محدثة بالفعل", + "updated_upstream_dns_toast": "تم حفظ خوادم Upstream بنجاح", + "dns_test_ok_toast": "تعمل خوادم DNS المحددة بشكل صحيح", + "dns_test_not_ok_toast": "خادم \"{{key}}\": لا يمكن استخدامه يرجى التحقق من كتابته بشكل صحيح", + "unblock": "إلغاء الحظر", + "block": "حظر", + "disallow_this_client": "منع هذا العميل", + "allow_this_client": "السماح لهذا العميل", + "block_for_this_client_only": "احجب هذا العميل فقط", + "unblock_for_this_client_only": "إلغاء حجب هذا العميل فقط", + "time_table_header": "وقت", + "date": "التاريخ", + "domain_name_table_header": "اسم النطاق", + "domain_or_client": "الدومين أو العميل", + "type_table_header": "النوع", + "response_table_header": "استجابة", + "response_code": "كود الاستجابة", + "client_table_header": "عميل", + "empty_response_status": "فارغ", + "show_all_filter_type": "إظهار الكل", + "show_filtered_type": "إظهار ماتمت تصفيته", + "no_logs_found": "لم يتم العثور على سجلات", + "refresh_btn": "تحديث", + "previous_btn": "السابق", + "next_btn": "التالي", + "loading_table_status": "جار التحميل...", + "page_table_footer_text": "الصفحة", + "rows_table_footer_text": "صفوف", + "updated_custom_filtering_toast": "تحديث قواعد الفلترة المخصصة", + "rule_removed_from_custom_filtering_toast": "تم إزالة قاعدة من قواعد الفلترة المخصصة: {{rule}}", + "rule_added_to_custom_filtering_toast": "تم إضافة إلى قواعد الفلترة المخصصة: {{rule}}", + "query_log_response_status": "الحالات: {{value}}", + "query_log_filtered": "تم الفلترة بواسطة {{filter}}", + "query_log_confirm_clear": "هل أنت متأكد من أنك تريد محو كامل سجل التصفية؟", + "query_log_cleared": "تم مسح سجل الاستعلام بنجاح", + "query_log_updated": "تم تحديث سجل الاستعلام بنجاح", + "query_log_clear": "مسح سجلات الاستعلام", + "query_log_retention": "الاحتفاظ بسجلات الاستعلام", + "query_log_enable": "تمكين السجل", + "query_log_configuration": "تكوين السجلات", + "query_log_disabled": "سجل الاستعلام معطل ويمكن تهيئته من<0>الاعدادات", + "query_log_strict_search": "استخدم علامات الاقتباس المزدوجة للبحث الدقيق", + "query_log_retention_confirm": "هل أنت متأكد من أنك تريد تغيير الاحتفاظ بسجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني سيتم فقدان بعض البيانات", + "anonymize_client_ip": "إخفاء عنوان IP العميل", + "anonymize_client_ip_desc": "لا تقم بحفظ كامل عنوان IP العميل في السجلات والإحصائيات", + "dns_config": "إعداد خادم DNS", + "dns_cache_config": "ضبط الملفات المؤقتة لـ DNS", + "dns_cache_config_desc": "هنا تستطيع ضبط اعدادات الـ DNS وملفاته", + "blocking_mode": "وضع الحجب", + "default": "إفتراضي", + "nxdomain": "NXDOMAIN", + "refused": "مرفوض", + "null_ip": "عنوان IP فارغ", + "custom_ip": "عنوان IP مخصص", + "blocking_ipv4": "حجب عنوان IPv4", + "blocking_ipv6": "حجب عنوان IPv6", + "dnscrypt": "DNSCrypt", + "dns_over_https": "DNS-over-HTTPS", + "dns_over_tls": "DNS-over-TLS", + "dns_over_quic": "DNS-over-QUIC", + "client_id": "عنوان العميل الشخصي", + "client_id_placeholder": "ادخل عنوان العميل الشخصي", + "client_id_desc": "يمكن تحديد هوية العميل. اعرف المزيد عن كيفية تحديد هوية العملاء هنا.", + "download_mobileconfig_doh": "حمّل .mobileconfig for DNS-over-HTTPS", + "download_mobileconfig_dot": "حمل .mobileconfig for DNS-over-TLS", + "download_mobileconfig": "حمّل ملف الإعدادات", + "plain_dns": "عنوان DNS العادي", + "form_enter_rate_limit": "ادخل حد التقييم", + "rate_limit": "حدود التقييم", + "edns_enable": "فعل EDNS client subnet", + "edns_cs_desc": "أضف EDNS الشبكة الفرعية للعميل (ECS) إلى الطلبات الأولية وقم بتسجيل القيم المرسلة من قبل العملاء في سجل الاستعلام.", + "rate_limit_desc": "عدد الطلبات في الثانية المسموح بها لكل عميل. جعله على 0 يعني عدم وجود حد.", + "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", + "blocking_mode_refused": "مرفوض: رد برمز مرفوض", + "blocking_mode_nxdomain": "NXDOMAIN: الرد باستخدام رمز NXDOMAIN", + "blocking_mode_null_ip": "IP Null: الاستجابة بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA)", + "blocking_mode_custom_ip": "استجابة IP مخصصة بعنوان IP تم تعيينه يدويًا", + "upstream_dns_client_desc": "إذا احتفظت بهذا الحقل فارغًا ، فسيستخدم AdGuard Home الخوادم التي تم تكوينها في<0>DNS إعدادات.", + "tracker_source": "مصدر المتعقب", + "source_label": "المصدر", + "found_in_known_domain_db": "تم العثور عليه في قاعدة بيانات دومينات معروفة.", + "category_label": "الفئة", + "rule_label": "قواعد", + "list_label": "قائمه", + "unknown_filter": "فلتر غير معروف {{filterId}}", + "known_tracker": "متعقب معروف", + "install_welcome_title": "مرحبًا بك في AdGuard Home!", + "install_welcome_desc": "AdGuard Home هو إعلان ومتتبع على مستوى الشبكة يمنع خادم DNS. الغرض منه هو السماح لك بالتحكم في شبكتك بأكملها وجميع أجهزتك، ولا يتطلب استخدام برنامج من جانب العميل.", + "install_settings_title": "واجهة ويب المسؤول", + "install_settings_listen": "واجهة الاستماع", + "install_settings_port": "المنفذ", + "install_settings_interface_link": "ستكون واجهة الويب الخاصة بمسؤول AdGuard Home متاحة على العناوين التالية:", + "form_error_port": "أدخل رقم منفذ صالح", + "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_username_enter": "أدخل اسم المستخدم", + "install_auth_password_enter": "أدخل كلمة المرور", + "install_step": "خطوة", + "install_devices_title": "قم بإعداد أجهزتك", + "install_devices_desc": "لبدء استخدام AdGuard Home، تحتاج إلى إعداد أجهزتك لاستخدامها.", + "install_submit_title": "تهانينا!", + "install_submit_desc": "انتهى إجراء الإعداد وأنت على استعداد لبدء استخدام AdGuard Home", + "install_devices_router": "راوتر", + "install_devices_router_desc": "يغطي هذا الإعداد تلقائيا جميع الأجهزة المتصلة بجهاز التوجيه المنزلي، دون الحاجة إلى تكوين كل منها يدويا.", + "install_devices_address": "يستمع خادم AdGuard Home DNS إلى العناوين التالية", + "install_devices_router_list_1": "افتح تفضيلات جهاز التوجيه الخاص بك. عادة، يمكنك الوصول إليه من متصفحك عبر عنوان URL، مثل http://192.168.0.1/ أو http://192.168.1.1/. قد يطلب منك إدخال كلمة مرور. إذا كنت لا تتذكر ذلك، يمكنك في كثير من الأحيان إعادة تعيين كلمة المرور عن طريق الضغط على زر في جهاز التوجيه نفسه، ولكن كن على علم بأنه إذا تم اختيار هذا الإجراء، فمن المحتمل أن تفقد إعدادات جهاز التوجيه بأكمله. إذا كان جهاز التوجيه الخاص بك يتطلب تطبيقا لإعداده، فيرجى تثبيت التطبيق على هاتفك أو الكمبيوتر الشخصي واستخدامه للوصول إلى إعدادات جهاز التوجيه.", + "install_devices_router_list_2": "ابحث عن إعدادات DHCP / DNS. ابحث عن أحرف DNS بجوار الحقل الذي يسمح بمجموعتين أو ثلاث مجموعات من الأرقام ، كل واحدة مقسمة إلى أربع مجموعات من واحد إلى ثلاثة أرقام.", + "install_devices_router_list_3": "أدخل عناوين خادم AdGuard Home هناك.", + "install_devices_router_list_4": "في بعض أنواع أجهزة التوجيه ، لا يمكن إعداد خادم DNS مخصص. في هذه الحالة ، قد يساعد إعداد AdGuard Home باعتباره <0>خادم DHCP. بخلاف ذلك ، يجب عليك التحقق من دليل جهاز التوجيه حول كيفية تخصيص خوادم DNS على طراز جهاز التوجيه المحدد الخاص بك.", + "install_devices_windows_list_1": "افتح لوحة التحكم من خلال قائمة ابدأ أو بحث Windows.", + "install_devices_windows_list_2": "انتقل إلى فئة الشبكة والإنترنت ثم إلى مركز الشبكة والمشاركة.", + "install_devices_windows_list_3": "على الجانب الأيسر من الشاشة ، ابحث عن \"تغيير إعدادات المحول\" وانقر عليها.", + "install_devices_windows_list_4": "حدد اتصالك النشط ، وانقر فوقه بزر الماوس الأيمن واختر خصائص.", + "install_devices_windows_list_5": "ابحث عن \"Internet Protocol Version 4 (TCP / IPv4)\" (أو ، لـ IPv6 ، \"Internet Protocol Version 6 (TCP / IPv6)\") في القائمة ، حدده ثم انقر فوق خصائص مرة أخرى.", + "install_devices_windows_list_6": "اختر \"استخدام عناوين خادم DNS التالية\" وأدخل عناوين خادم AdGuard Home.", + "install_devices_macos_list_1": "انقر فوق أيقونة Apple وانتقل إلى تفضيلات النظام.", + "install_devices_macos_list_2": "اضغط على الشبكة.", + "install_devices_macos_list_3": "حدد الاتصال الأول في قائمتك وانقر فوق خيارات متقدمة.", + "install_devices_macos_list_4": "حدد علامة التبويب DNS وأدخل عناوين خادم AdGuard Home.", + "install_devices_android_list_1": "من الشاشة الرئيسية لقائمة Android ، انقر فوق الإعدادات.", + "install_devices_android_list_2": "اضغط على Wi-Fi في القائمة. ستظهر الشاشة التي تسرد جميع الشبكات المتاحة (من المستحيل تعيين DNS مخصص لاتصال المحمول).", + "install_devices_android_list_3": "اضغط لفترة طويلة على الشبكة التي تتصل بها ثم اضغط على تعديل الشبكة", + "install_devices_android_list_4": "في بعض الأجهزة قد تحتاج إلى تحديد المربع المتقدم لرؤية المزيد من الإعدادات لضبط إعدادات DNS لنظام اندرويد ستحتاج إلى تبديل إعدادات IP من DHCP إلى ثابت.", + "install_devices_android_list_5": "قم بتغيير قيم DNS 1 و DNS 2 المعينة لعناوين خادم AdGuard Home", + "install_devices_ios_list_1": "من الشاشة الرئيسية انقر فوق الإعدادات", + "install_devices_ios_list_2": "اختر Wi-Fi في القائمة اليسرى (من المستحيل ضبط الـ DNS لشبكات الجوال).", + "install_devices_ios_list_3": "اضغط على اسم الشبكة النشطة حاليًا.", + "install_devices_ios_list_4": "في حقل DNS ، أدخل عناوين خادم AdGuard Home.", + "get_started": "أبدأ", + "next": "التالي", + "open_dashboard": "افتح لوحة التحكم", + "install_saved": "تم الحفظ بنجاح", + "encryption_title": "التعمية", + "encryption_desc": "دعم التشفير (HTTPS / TLS) لكل من DNS وواجهة ويب المسؤول", + "encryption_config_saved": "تم حفظ اعدادات التشفير", + "encryption_server": "اسم الخادم", + "encryption_server_enter": "ادخل عنوان النطاق الخاص بك", + "encryption_redirect": "إعادة التوجيه إلى HTTPS تلقائيًا", + "encryption_redirect_desc": "إذا تم تحديده ، فسيقوم AdGuard Home بإعادة توجيهك تلقائيًا من عناوين HTTP إلى عناوين HTTPS.", + "encryption_https": "منفذ HTTPS", + "encryption_https_desc": "إذا تم تكوين منفذ HTTPS ، فسيتم الوصول إلى واجهة مشرف AdGuard Home عبر HTTPS ، وستوفر أيضًا DNS-over-HTTPS على موقع '/dns-query'.", + "encryption_dot": "منفذ DNS-over-TLS", + "encryption_dot_desc": "إذا تم ضبط هذا المنفذ ، فسيقوم AdGuard Home بتشغيل خادم DNS-over-TLS على هذا المنفذ.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "إذا تم ضبط هذا المنفذ، فسيقوم AdGuard Home بتشغيل خادم DNS-over-QUIC على هذا المنفذ.", + "encryption_certificates": "الشهادات", + "encryption_certificates_desc": "من أجل استخدام التشفير ، تحتاج إلى تقديم سلسلة شهادات SSL صالحة لنطاقك. يمكنك الحصول على شهادة مجانية على <0>{{link}} أو يمكنك شرائها من أحد المراجع المصدقة الموثوقة.", + "encryption_certificates_input": "انسخ / الصق الشهادات المشفرة PEM هنا.", + "encryption_status": "الحالة", + "encryption_expire": "يتنهي في", + "encryption_key": "مفتاح خاص", + "encryption_key_input": "انسخ / الصق مفتاحك الخاص المشفر بـ PEM لشهادتك هنا", + "encryption_enable": "تمكين التشفير (HTTPS و DNS-over-HTTPS و DNS-over-TLS)", + "encryption_enable_desc": "إذا تم تمكين التشفير فستعمل واجهة مسؤول AdGuard Home عبر HTTPS وسيستمع خادم DNS للطلبات عبر DNS-over-HTTPS و DNS-over-TLS.", + "encryption_chain_valid": "سلسلة الشهادات صالحة", + "encryption_chain_invalid": "سلسلة الشهادات غير صالحة", + "encryption_key_valid": "هذا مفتاح خاص {{type}} صالح", + "encryption_key_invalid": "هذا مفتاح خاص {{type}} غير صالح", + "encryption_subject": "الموضوع", + "encryption_issuer": "المصدر", + "encryption_hostnames": "اسم المستضيف", + "encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟", + "topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير.", + "topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير.", + "form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535", + "form_error_port_unsafe": "منفذ غير آمن", + "form_error_equal": "يجب ألا تكون متساوية", + "form_error_password": "كلمة السر غير مطابقة", + "reset_settings": "إعادة ضبط الإعدادات", + "update_announcement": "AdGuard Home {{version}} متوفر الآن! <0>انقر هنا لمزيد من المعلومات.", + "setup_guide": "دليل الإعداد", + "dns_addresses": "عناوين DNS", + "dns_start": "خادم DNS قيد التشغيل", + "dns_status_error": "خطأ في التحقق من حالة خادم الـ DNS", + "down": "تحت", + "fix": "يصلح", + "dns_providers": "فيما يلي قائمة <0> بموفري DNS المعروفين للاختيار من بينها.", + "update_now": "تحديث الآن", + "update_failed": "فشل التحديث التلقائي. الرجاء اتباع هذه الخطوات للتحديث يدويًا.", + "manual_update": "الرجاء اتباع هذه الخطوات للتحديث يدويًا.", + "processing_update": "يُرجى الانتظار ، يتم تحديث صفحة AdGuard الرئيسية", + "clients_title": "العملاء الدائمين", + "clients_desc": "قم بضبط سجلات العميل الدائمة للأجهزة المتصلة بـ AdGuard Home", + "settings_global": "عالمي", + "settings_custom": "مخصص", + "table_client": "العميل", + "table_name": "الاسم", + "save_btn": "حفظ", + "client_add": "إضافة عميل", + "client_new": "عميل جديد", + "client_edit": "تعديل العميل", + "client_identifier": "المعّرف", + "ip_address": "عنوان IP", + "client_identifier_desc": "يمكن التعرف على العملاء من خلال عنوان IP أو CIDR أو عنوان MAC أو ClientID (يمكن استخدامه في DoT / DoH / DoQ). تعرف على المزيد حول كيفية تحديد العملاء <0> هنا .", + "form_enter_ip": "ادخل عنوان IP", + "form_enter_subnet_ip": "أدخل عنوان IP في الشبكة الفرعية \"{{cidr}}\"", + "form_enter_mac": "ادخل MAC", + "form_enter_id": "ادخل المعّرف", + "form_add_id": "أضافة معّرف", + "form_client_name": "ادخل اسم العميل", + "name": "اسم", + "client_global_settings": "استخدم إعدادات عالمية", + "client_deleted": "تم حذف العميل \"{{key}}\" بنجاح", + "client_added": "تم اضافة العميل \"{{key}}\" بنجاح", + "client_updated": "تم تحديث العميل \"{{key}}\" بنجاح", + "clients_not_found": "لم يتم العثور على عملاء", + "client_confirm_delete": "هل أنت متأكد من أنك تريد حذف العميل \"{{key}}\"?", + "list_confirm_delete": "هل أنت متأكد أنك تريد حذف هذه القائمة؟", + "auto_clients_title": "Runtime clients", + "auto_clients_desc": "الأجهزة غير المدرجة في قائمة العملاء الدائمين الذين قد لا يزالون يستخدمون AdGuard Home", + "access_title": "إعدادات الوصول", + "access_desc": "هنا يمكنك ضبط قواعد الوصول لخادم AdGuard Home DNS", + "access_allowed_title": "العملاء المسموحين", + "access_allowed_desc": "قائمة CIDRs أو عناوين IP أو ClientIDs . إذا كانت هذه القائمة تحتوي على إدخالات ، فسيقبل AdGuard Home الطلبات من هؤلاء العملاء فقط.", + "access_disallowed_title": "العملاء غير المسموحين", + "access_disallowed_desc": "قائمة CIDRs أو عناوين IP أو ClientIDs . إذا كانت هذه القائمة تحتوي على إدخالات ، فسيقوم AdGuard Home بإسقاط الطلبات من هؤلاء العملاء. يتم تجاهل هذا الحقل إذا كانت هناك إدخالات في العملاء المسموح لهم.", + "access_blocked_title": "النطاقات غير المسموح بها", + "access_blocked_desc": "لا ينبغي الخلط بينه وبين المرشحات. يسقط AdGuard Home استعلامات DNS المطابقة لهذه المجالات ، ولا تظهر هذه الاستعلامات حتى في سجل الاستعلام. يمكنك تحديد أسماء النطاقات الدقيقة أو أحرف البدل أو قواعد تصفية عناوين URL ، على سبيل المثال \"example.org\" أو \"*.example.org\" أو \"|| example.org ^\" في المقابل.", + "access_settings_saved": "تم حفظ إعدادات الوصول بنجاح", + "updates_checked": "يتوفر إصدار جديد من AdGuard Home", + "updates_version_equal": "AdGuard Home محدث", + "check_updates_now": "تحقق من وجود تحديثات الآن", + "dns_privacy": "خصوصية DNS", + "setup_dns_privacy_1": "<0> DNS-over-TLS: استخدم سلسلة <1> {{address}} .", + "setup_dns_privacy_2": "<0> DNS-over-HTTPS: استخدم سلسلة <1> {{address}} .", + "setup_dns_privacy_3": "<0> فيما يلي قائمة بالبرامج التي يمكنك استخدامها. ", + "setup_dns_privacy_4": "على جهاز iOS 14 أو macOS Big Sur ، يمكنك تنزيل ملف \".mobileconfig\" خاص يضيف خوادم DNS-over-HTTPS أو DNS-over-TLS إلى إعدادات DNS.", + "setup_dns_privacy_android_1": "يدعم Android 9 DNS-over-TLS أصلاً. لتكوينه ، انتقل إلى الإعدادات → الشبكة والإنترنت → متقدم → DNS الخاص وأدخل اسم المجال الخاص بك هناك.", + "setup_dns_privacy_android_2": "<0> AdGuard لنظام Android يدعم <1> DNS-over-HTTPS و <1> DNS-over-TLS .", + "setup_dns_privacy_android_3": "<0> Intra يضيف دعم <1> DNS-over-HTTPS إلى Android.", + "setup_dns_privacy_ios_1": "<0> DNSCloak يدعم <1> DNS-over-HTTPS ، ولكن من أجل تكوينه لاستخدام الخادم الخاص بك ، ستحتاج إلى إنشاء <2> DNS Stamp لذلك.", + "setup_dns_privacy_ios_2": "<0> AdGuard لنظام iOS يدعم إعداد <1> DNS-over-HTTPS و <1> DNS-over-TLS الإعداد.", + "setup_dns_privacy_other_title": "تطبيقات أخرى", + "setup_dns_privacy_other_1": "يمكن أن يكون AdGuard Home نفسه عميل DNS آمنًا على أي نظام أساسي.", + "setup_dns_privacy_other_2": "يدعم <0> dnsproxy جميع بروتوكولات DNS الآمنة المعروفة.", + "setup_dns_privacy_other_3": "<0> dnscrypt-proxy يدعم <1> DNS-over-HTTPS .", + "setup_dns_privacy_other_4": "يدعم <0> Mozilla Firefox <1> DNS-over-HTTPS .", + "setup_dns_privacy_other_5": "ستجد المزيد من التطبيقات <0> هنا و <1> هنا .", + "setup_dns_privacy_ioc_mac": "اعدادات iOS و macOS", + "setup_dns_notice": "من أجل استخدام <0> DNS-over-HTTPS أو <1> DNS-over-TLS ، تحتاج إلى <1> تكوين التشفير في إعدادات AdGuard Home.", + "rewrite_added": "تمت إضافة إعادة كتابة DNS لـ \"{{key}}\" بنجاح", + "rewrite_deleted": "تم حذف إعادة كتابة DNS لـ \"{{key}}\" بنجاح", + "rewrite_add": "إضافة إعادة كتابة DNS", + "rewrite_not_found": "لم يتم العثور على إعادة كتابة DNS", + "rewrite_confirm_delete": "هل أنت متأكد من أنك تريد حذف إعادة كتابة DNS لـ \"{{key}}\"؟", + "rewrite_desc": "يسمح بتكوين استجابة DNS المخصصة بسهولة لاسم نطاق معين.", + "rewrite_applied": "يتم تطبيق قاعدة إعادة الكتابة", + "rewrite_hosts_applied": "أعيد كتابتها بواسطة قاعدة ملف المضيفين", + "dns_rewrites": "إعادة كتابة DNS", + "form_domain": "أدخل اسم النطاق أو حرف البدل", + "form_answer": "أدخل عنوان IP أو اسم النطاق", + "form_error_domain_format": "تنسيق النطاق غير صالح", + "form_error_answer_format": "تنسيق إجابة غير صالح", + "configure": "ضبط", + "main_settings": "الاعدادات الرئيسية", + "block_services": "حظر خدمات معينة", + "blocked_services": "الخوادم المحجوبة", + "blocked_services_desc": "يسمح بحجب المواقع والخدمات الشعبية بسرعة.", + "blocked_services_saved": "تم حفظ الخوادم المحجوبة بنجاح", + "blocked_services_global": "استخدام خدمات الحظر العالمية", + "blocked_service": "الخدمات المحجوبة", + "block_all": "حجب الكل", + "unblock_all": "إلغاء حجب الكل", + "encryption_certificate_path": "مسار الشهادة", + "encryption_private_key_path": "مسار المفتاح الخاص", + "encryption_certificates_source_path": "قم بتعيين مسار ملف الشهادات", + "encryption_certificates_source_content": "الصق محتويات الشهادات", + "encryption_key_source_path": "قم بتعيين ملف مفتاح خاص", + "encryption_key_source_content": "الصق محتويات المفتاح الخاص", + "stats_params": "ضبط الاحصائيات", + "config_successfully_saved": "تم حفظ الاعدادات بنجاح", + "interval_6_hour": "ساعات6", + "interval_24_hour": "24 ساعة", + "interval_days": "{{count}} يوم", + "interval_days_plural": "{{count}} الأيام", + "domain": "النطاق", + "ecs": "ECS", + "punycode": "Punycode", + "answer": "الإجابة", + "filter_added_successfully": "تم إضافة القائمة بنجاح", + "filter_removed_successfully": "تم ازالته من القائمة بنجاح", + "filter_updated": "تم تحديث القائمة بنجاح", + "statistics_configuration": "ضبط الاحصائيات", + "statistics_retention": "الاحتفاظ بالإحصاءات", + "statistics_retention_desc": "إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات", + "statistics_clear": "إعادة تعيين الإحصائيات", + "statistics_clear_confirm": "هل أنت متأكد من أنك تريد مسح الإحصاءات؟", + "statistics_retention_confirm": "هل أنت متأكد أنك تريد تغيير الاحتفاظ بالإحصاءات؟ إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات", + "statistics_cleared": "تم مسح الإحصائيات بنجاح", + "statistics_enable": "تفعيل الاحصائيات", + "interval_hours": "{{count}} ساعة", + "interval_hours_plural": "{{count}} ساعات", + "filters_configuration": "اضبط الفلاتر", + "filters_enable": "تفعيل الفلاتر", + "filters_interval": "الفاصل الزمني لتحديث الفلاتر", + "disabled": "معطلة", + "username_label": "اسم المستخدم", + "username_placeholder": "ادخل اسم المستخدم", + "password_label": "كلمة المرور", + "password_placeholder": "ادخل كلمة المرور", + "sign_in": "تسجيل الدخول", + "sign_out": "تسجيل الخروج", + "forgot_password": "نسيت كلمة المرور؟", + "forgot_password_desc": "يرجى اتباع <0> هذه الخطوات لإنشاء كلمة مرور جديدة لحساب المستخدم الخاص بك.", + "location": "الموقع", + "orgname": "اسم المنظمة", + "netname": "اسم الشبكة", + "network": "الشبكة", + "descr": "الوصف", + "whois": "WHOIS", + "filtering_rules_learn_more": "<0> اعرف المزيد حول إنشاء قوائم المضيفين الخاصة بك.", + "blocked_by_response": "حظر بواسطة CNAME or IP in response", + "blocked_by_cname_or_ip": "حظر بواسطة CNAME or IP", + "try_again": "حاول مرة أخرى", + "domain_desc": "أدخل اسم النطاق أو حرف البدل الذي تريد إعادة كتابته.", + "example_rewrite_domain": "أعد كتابة الردود لاسم النطاق هذا فقط.", + "example_rewrite_wildcard": "أعد كتابة الردود لجميع النطاقات الفرعية <0> example.org .", + "rewrite_ip_address": "عنوان IP: استخدم عنوان IP هذا في استجابة A أو AAAA", + "rewrite_domain_name": "اسم النطاق: أضف سجل CNAME", + "rewrite_A": "<0> A : قيمة خاصة ، احتفظ بسجلات <0> A من upstream", + "rewrite_AAAA": "<0> AAAA : قيمة خاصة ، احتفظ بسجلات <0> AAAA من upstream", + "disable_ipv6": "قم بتعطيل تحليل عناوين IPv6", + "disable_ipv6_desc": "قم بإسقاط جميع استعلامات DNS لعناوين IPv6 (اكتب AAAA).", + "fastest_addr": "أسرع عنوان IP", + "fastest_addr_desc": "استعلم عن جميع خوادم DNS وأعد عنوان IP الأسرع بين جميع الاستجابات. يؤدي هذا إلى إبطاء استعلامات DNS حيث يتعين على AdGuard Home انتظار الاستجابات من جميع خوادم DNS ، ولكنه يحسن الاتصال الكلي.", + "autofix_warning_text": "إذا قمت بالنقر فوق \"إصلاح\" ، فسيقوم AdGuard Home بتهيئة نظامك لاستخدام خادم AdGuard Home DNS.", + "autofix_warning_list": "سيقوم بتنفيذ هذه المهام: <0> إلغاء تنشيط نظام DNSStubListener <0> تعيين عنوان خادم DNS إلى 127.0.0.1 <0> استبدال هدف الارتباط الرمزي لـ /etc/resolv.conf بـ / run / systemd /resolve/resolv.conf <0> إيقاف DNSStubListener (إعادة تحميل خدمة حل نظام d) ", + "autofix_warning_result": "نتيجة لذلك ، ستتم معالجة جميع طلبات DNS من نظامك بواسطة AdGuard Home افتراضيًا.", + "tags_title": "وسوم", + "tags_desc": "يمكنك تحديد العلامات التي تتوافق مع العميل. قم بتضمين العلامات في قواعد التصفية لتطبيقها بدقة أكبر. <0> معرفة المزيد .", + "form_select_tags": "حدد علامات العميل", + "check_title": "تحقق من الفلترة", + "check_desc": "تحقق مما إذا تم فلترة اسم المضيف.", + "check": "تحقق", + "form_enter_host": "ادخل اسم المضيف", + "filtered_custom_rules": "تمت تصفيتها حسب قواعد التصفية المخصصة", + "choose_from_list": "اختر من القائمة", + "add_custom_list": "أضف قائمة مخصصة", + "host_whitelisted": "المضيف مسموح به", + "check_ip": "عناوين الـ IP: {{ip}}", + "check_cname": "CNAME: {{cname}}", + "check_reason": "سبب: {{reason}}", + "check_service": "أسم الخدمة: {{service}}", + "service_name": "أسم الخدمة", + "check_not_found": "غير موجود في قوائم التصفية الخاصة بك", + "client_confirm_block": "هل أنت متأكد من أنك تريد منع العميل \"{{ip}}\"؟", + "client_confirm_unblock": "هل تريد بالتأكيد إلغاء حظر العميل \"{{ip}}\"؟", + "client_blocked": "تم حظر العميل \"{{ip}}\" بنجاح", + "client_unblocked": "تم إلغاء حظر العميل \"{{ip}}\" بنجاح", + "static_ip": "عنوان IP ثابت", + "static_ip_desc": "AdGuard Home هو خادم لذلك يحتاج إلى عنوان IP ثابت ليعمل بشكل صحيح. خلاف ذلك ، في مرحلة ما ، قد يقوم جهاز التوجيه الخاص بك بتعيين عنوان IP مختلف لهذا الجهاز.", + "set_static_ip": "قم بتعيين عنوان IP ثابت", + "install_static_ok": "أخبار جيدة! تم ضبط عنوان IP الثابت بالفعل", + "install_static_error": "لا يمكن لـ AdGuard Home تكوينه تلقائيًا لواجهة الشبكة هذه. الرجاء البحث عن تعليمات حول كيفية القيام بذلك يدويًا.", + "install_static_configure": "اكتشف AdGuard Home استخدام عنوان IP الديناميكي <0> {{ip}} . هل تريد تعيينه كعنوان ثابت؟", + "confirm_static_ip": "سيقوم AdGuard Home بتهيئة {{ip}} ليكون عنوان IP الثابت الخاص بك. هل تريد المتابعة؟", + "list_updated": "قائمة {{count}} محدثة", + "list_updated_plural": "قوائم {{count}} محدثة", + "dnssec_enable": "تفعيل DNSSEC", + "dnssec_enable_desc": "قم بتعيين علامة DNSSEC في استعلامات DNS الواردة وتحقق من النتيجة (مطلوب محلل يدعم DNSSEC).", + "validated_with_dnssec": "تم التحقق من صحتها باستخدام DNSSEC", + "all_queries": "كافة الاستفسارات", + "show_blocked_responses": "حظر", + "show_whitelisted_responses": "القائمة البيضاء", + "show_processed_responses": "المعالجة", + "blocked_safebrowsing": "محظور بواسطة التصفح الآمن", + "blocked_adult_websites": "محظور بواسطة الرقابة الأبوية", + "blocked_threats": "التهديدات المحظورة", + "allowed": "القائمة البيضاء", + "filtered": "تمت الفلترة", + "rewritten": "أعيدت كتابته", + "safe_search": "البحث الأمن", + "blocklist": "قائمة الحظر", + "milliseconds_abbreviation": "ms", + "cache_size": "حجم ذاكرة التخزين المؤقت", + "cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات (بالبايت).", + "cache_ttl_min_override": "تجاوز الحد الأدنى من مدة البقاء TTL", + "cache_ttl_max_override": "تجاوز الحد الاقصى من مدة البقاء TTL", + "enter_cache_size": "أدخل حجم ذاكرة التخزين المؤقت (بايت)", + "enter_cache_ttl_min_override": "أدخل الحد الأدنى من مدة البقاء (بالثواني)", + "enter_cache_ttl_max_override": "أدخل الحد الاقصى من مدة البقاء (بالثواني)", + "cache_ttl_min_override_desc": "قم بتمديد قيم فترة البقاء القصيرة (بالثواني) المستلمة من الخادم الرئيسي عند تخزين استجابات DNS مؤقتًا.", + "cache_ttl_max_override_desc": "قم بتعيين الحد الأقصى لقيمة الوقت للعيش (بالثواني) للإدخالات في ذاكرة التخزين المؤقت لنظام أسماء النطاقات.", + "ttl_cache_validation": "يجب أن يكون الحد الأدنى لتجاوز TTL لذاكرة التخزين المؤقت أقل من أو يساوي الحد الأقصى", + "cache_optimistic": "متفائل التخزين المؤقت", + "cache_optimistic_desc": "اجعل AdGuard Home يستجيب من ذاكرة التخزين المؤقت حتى عندما تنتهي صلاحية الإدخالات وحاول أيضًا تحديثها.", + "filter_category_general": "General", + "filter_category_security": "الامان", + "filter_category_regional": "إقليمي", + "filter_category_other": "أخرى", + "filter_category_general_desc": "القوائم التي تمنع التتبع والإعلان على معظم الأجهزة", + "filter_category_security_desc": "القوائم المصممة خصيصًا لحظر النطاقات الخبيثة والتصيد الاحتيالي والخداع", + "filter_category_regional_desc": "القوائم التي تركز على الإعلانات الإقليمية وخوادم التتبع", + "filter_category_other_desc": "قوائم حظر أخرى", + "setup_config_to_enable_dhcp_server": "أضبط الاعدادات لتمكين خادم DHCP", + "original_response": "الرد الأصلي", + "click_to_view_queries": "انقر لعرض الـ queries", + "port_53_faq_link": "غالبًا ما يتم احتلال المنفذ 53 بواسطة خدمات \"DNSStubListener\" أو \"حل النظام\". يرجى قراءة <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}} (يتم تقديمه من ذاكرة التخزين المؤقت)", + "form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل" +} diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index c01cbaa4..1f21106e 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -47,6 +47,7 @@ "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": "Павінна быць больш за пачатак дыяпазону", @@ -70,8 +71,8 @@ "dhcp_error": "AdGuard Home не можа вызначыць, ці ёсць у сетцы іншы актыўны DHCP-сервер", "dhcp_static_ip_error": "Для таго, каб выкарыстоўваць DHCP-сервер, павінен быць усталяваны статычны IP-адрас. Мы не змаглі вызначыць, ці выкарыстоўвае гэты інтэрфейс сеціва статычны IP-адрас. Калі ласка, усталюйце яго ручна.", "dhcp_dynamic_ip_found": "Ваша сістэма выкарыстоўвае дынамічны IP-адрас для інтэрфейсу <0>{{interfaceName}}. Каб выкарыстоўваць DHCP-сервер трэба ўсталяваць статычны IP-адрас. Ваш бягучы IP-адрас – <0>{{ipAddress}}. Мы аўтаматычна ўсталюем яго як статычны, калі вы націснеце кнопку Ўключыць DHCP.", - "dhcp_lease_added": "Статычная арэнда \"{{key}}\" паспяхова дададзена", - "dhcp_lease_deleted": "Статычная арэнда \"{{key}}\" паспяхова выдалена", + "dhcp_lease_added": "Статычная арэнда «{{key}}» паспяхова дададзена", + "dhcp_lease_deleted": "Статычная арэнда «{{key}}» паспяхова выдалена", "dhcp_new_static_lease": "Новая статычная арэнда", "dhcp_static_leases_not_found": "Не знойдзена статычных арэнд DHCP", "dhcp_add_static_lease": "Дадаць статычную арэнду", @@ -81,7 +82,7 @@ "dhcp_reset": "Вы ўпэўнены, што хочаце скінуць налады DHCP?", "country": "Краіна", "city": "Горад", - "delete_confirm": "Вы ўпэўнены, што хочаце выдаліць \"{{key}}\"?", + "delete_confirm": "Вы ўпэўнены, што хочаце выдаліць «{{key}}»?", "form_enter_hostname": "Увядзіце імя хаста", "error_details": "Дэталізацыя памылкі", "response_details": "Дэталі адказу", @@ -114,7 +115,7 @@ "dns_query": "DNS-запыты", "blocked_by": "<0>Заблакавана фільтрамі", "stats_malware_phishing": "Заблакаваныя шкодныя і фішынгавыя сайты", - "stats_adult": "Заблакаваныя \"дарослыя\" сайты", + "stats_adult": "Заблакаваныя «дарослыя» сайты", "stats_query_domain": "Часта запытаныя дамены", "for_last_24_hours": "за 24 гадзіны", "for_last_days": "за апошні {{count}} дзень", @@ -132,13 +133,13 @@ "number_of_dns_query_24_hours": "Колькасць DNS-запытаў за 24 гадзіны", "number_of_dns_query_blocked_24_hours": "Колькасць DNS-запытаў, заблакаваных фільтрамі і блок-спісамі", "number_of_dns_query_blocked_24_hours_by_sec": "Колькасць DNS-запытаў, заблакаваных модулем Антыфішынгу AdGuard", - "number_of_dns_query_blocked_24_hours_adult": "Колькасць заблакаваных \"сайтаў для дарослых\"", + "number_of_dns_query_blocked_24_hours_adult": "Колькасць заблакаваных «сайтаў для дарослых»", "enforced_save_search": "Ужыты бяспечны пошук", "number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук", "average_processing_time": "Сярэдні час апрацоўкі запыту", "average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах", "block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў", - "filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў \"Фільтрах\".", + "filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «Фільтрах».", "use_adguard_browsing_sec": "Выкарыстаць Бяспечную навігацыю AdGuard", "use_adguard_browsing_sec_hint": "AdGuard Home праверыць, ці ўлучаны дамен у ўэб-службу бяспекі браўзара. Ён будзе выкарыстоўваць API, каб выканаць праверку: на сервер адсылаецца толькі кароткі прэфікс імя дамена SHA256.", "use_adguard_parental": "Ужывайце модуль Бацькоўскага кантролю AdGuard ", @@ -213,14 +214,15 @@ "example_upstream_udp": "звычайны DNS (праз UDP, імя хаста);", "example_upstream_dot": "зашыфраваны <0>DNS-over-TLS;", "example_upstream_doh": "зашыфраваны <0>DNS-over-HTTPS;", - "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC (эксперыментальны);", + "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps для <1>DNSCrypt ці <2>DNS-over-HTTPS рэзалвераў;", "example_upstream_tcp": "звычайны DNS (наўзверх TCP);", "example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);", "all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены", "updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены", "dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна", - "dns_test_not_ok_toast": "Сервер \"{{key}}\": немагчыма выкарыстоўваць, праверце слушнасць напісання", + "dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання", + "dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам", "unblock": "Адблакаваць", "block": "Заблакаваць", "disallow_this_client": "Забараніць доступ гэтаму кліенту", @@ -331,15 +333,15 @@ "install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.", "install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах", "install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.", - "install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары \"DNS\" поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.", + "install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.", "install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.", "install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці DHCP-сервера. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.", - "install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню \"Пуск\" ці праз пошук Windows.", - "install_devices_windows_list_2": "Перайдзіце ў \"Сеціва і інтэрнэт\", а потым у \"Цэнтр кіравання сеціва і агульным доступам\"", + "install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.", + "install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам»", "install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».", "install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.", - "install_devices_windows_list_5": "Знайдзіце ў спісе пункт \"IP версіі 4 (TCP/IPv4)\", вылучыце яго і потым ізноў націсніце \"Уласцівасці\".", - "install_devices_windows_list_6": "Абярыце \"Выкарыстаць наступныя адрасы DNS-сервераў\" і ўвядзіце адрас AdGuard Home.", + "install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».", + "install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы DNS-сервераў» і ўвядзіце адрас AdGuard Home.", "install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.", "install_devices_macos_list_2": "Клікніце па іконцы Сеціва.", "install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».", @@ -362,15 +364,15 @@ "encryption_config_saved": "Налады шыфравання захаваны", "encryption_server": "Імя сервера", "encryption_server_enter": "Увядзіце ваша даменавае імя", - "encryption_server_desc": "Для выкарыстання HTTPS вам трэба ўвесці імя сервера, якое падыходзіць вашаму SSL-сертыфікату.", + "encryption_server_desc": "Калі ўстаноўлена, AdGuard Home вызначае ClientID, адказвае на запыты DDR і выконвае дадатковыя праверкі злучэння. Калі не ўстаноўлена, гэтыя функцыі адключаны. Павінна адпавядаць аднаму з імёнаў DNS у сертыфікаце.", "encryption_redirect": "Аўтаматычна перанакіроўваць на HTTPS", "encryption_redirect_desc": "Калі ўлучана, AdGuard Home будзе аўтаматычна перанакіроўваць вас з HTTP на HTTPS адрас.", "encryption_https": "Порт HTTPS", "encryption_https_desc": "Калі порт HTTPS наладжаны, ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны праз HTTPS, а таксама DNS-over-HTTPS сервер будзе даступны па шляху '/dns-query'.", "encryption_dot": "Порт DNS-over-TLS", "encryption_dot_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць DNS-over-TLS-сервер на гэтаму порту.", - "encryption_doq": "Порт DNS-over-QUIC (эксперыментальны)", - "encryption_doq_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце. Гэта эксперыментальна і можа быць ненадзейна. Апроч таго, не так шмат кліентаў падтрымвае гэты спосаб цяпер.", + "encryption_doq": "Порт DNS-over-QUIC", + "encryption_doq_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце.", "encryption_certificates": "Сертыфікаты", "encryption_certificates_desc": "Для выкарыстання шыфравання вам трэба падаць валідны ланцужок SSL-сертыфікатаў для вашага дамена. Вы можаце атрымаць дармовы сертыфікат на <0>{{link}} ці вы можаце купіць яго ў аднаго з давераных Цэнтраў Сертыфікацыі.", "encryption_certificates_input": "Скапіюйце сюды сертыфікаты ў PEM-кадоўцы.", @@ -428,11 +430,11 @@ "form_client_name": "Увядзіце імя кліента", "name": "Назва", "client_global_settings": "Выкарыстаць глабальныя налады", - "client_deleted": "Кліент \"{{key}}\" паспяхова выдалены", - "client_added": "Кліент \"{{key}}\" паспяхова дададзены", - "client_updated": "Кліент \"{{key}}\" паспяхова абноўлены", + "client_deleted": "Кліент «{{key}}» паспяхова выдалены", + "client_added": "Кліент «{{key}}» паспяхова дададзены", + "client_updated": "Кліент «{{key}}» паспяхова абноўлены", "clients_not_found": "Кліентаў не знойдзена", - "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента \"{{key}}\"?", + "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента «{{key}}»?", "list_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць гэты спіс?", "auto_clients_title": "Кліенты (runtime)", "auto_clients_desc": "Прылады, якіх няма ў спісе пастаянных кліентаў, якія ўсё яшчэ могуць выкарыстоўваць AdGuard Home", @@ -445,7 +447,7 @@ "access_blocked_title": "Заблакаваныя дамены", "access_blocked_desc": "Не блытайце гэта з фільтрамі. AdGuard Home будзе ігнараваць DNS-запыты з гэтымі даменамі.", "access_settings_saved": "Налады доступу паспяхова захаваны", - "updates_checked": "Праверка абнаўленняў прайшла паспяхова", + "updates_checked": "Даступная новая версія AdGuard Home", "updates_version_equal": "Версія AdGuard Home актуальная", "check_updates_now": "Праверыць абнаўленні", "dns_privacy": "Зашыфраваны DNS", @@ -466,11 +468,11 @@ "setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут і <1>тут.", "setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS", "setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS ці <1>DNS-over-TLS, вам патрэбна <0>наладзіць шыфраванне у наладах AdGuard Home.", - "rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена", - "rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена", + "rewrite_added": "Правіла перанакіравання DNS для «{{key}}» паспяхова дададзена", + "rewrite_deleted": "Правіла перанакіравання DNS для «{{key}}» паспяхова выдалена", "rewrite_add": "Дадаць правіла перанакіравання DNS", "rewrite_not_found": "Не знойдзена правілаў перанакіравання DNS", - "rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для \"{{key}}\"?", + "rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для «{{key}}»?", "rewrite_desc": "Дазваляе лёгка наладзіць карыстацкі DNS-адказ для пэўнага дамена.", "rewrite_applied": "Ужыта правіла перанакіравання", "rewrite_hosts_applied": "Перапісана па правіле файла hosts", @@ -551,7 +553,7 @@ "disable_ipv6_desc": "Калі гэта опцыя ўлучана, усе DNS-запыты адрасоў IPv6 (тып AAAA) будуць ігнаравацца.", "fastest_addr": "Найхуткі IP-адрас", "fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.", - "autofix_warning_text": "Пры націску \"Выправіць\" AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.", + "autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.", "autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener <0>Усталяваць адрас сервера DNS на 127.0.0.1 <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу).", "autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n", "tags_title": "Тэгі", @@ -571,10 +573,10 @@ "check_service": "Назва сэрвісу: {{service}}", "service_name": "Назва сэрвіса", "check_not_found": "Не знойдзена ў вашым спісе фільтраў", - "client_confirm_block": "Вы ўпэўнены, што хочаце заблакаваць кліента \"{{ip}}\"?", - "client_confirm_unblock": "Вы ўпэўнены, што хочаце адблакаваць кліента \"{{ip}}\"?", - "client_blocked": "Кліент \"{{ip}}\" паспяхова заблакаваны", - "client_unblocked": "Кліент \"{{ip}}\" паспяхова адблакаваны", + "client_confirm_block": "Вы ўпэўнены, што хочаце заблакаваць кліента «{{ip}}»?", + "client_confirm_unblock": "Вы ўпэўнены, што хочаце адблакаваць кліента «{{ip}}»?", + "client_blocked": "Кліент «{{ip}}» паспяхова заблакаваны", + "client_unblocked": "Кліент «{{ip}}» паспяхова адблакаваны", "static_ip": "Статычны IP-адрас", "static_ip_desc": "AdGuard Home з'яўляецца серверам, таму для карэктнай працы яму патрэбен статычны IP-адрас. У адваротным выпадку, у нейкі момант ваш роўтар можа прысвоіць гэтай прыладзе іншы IP-адрас.", "set_static_ip": "Усталяваць статычны IP-адрас", @@ -623,7 +625,7 @@ "setup_config_to_enable_dhcp_server": "Наладзіць канфігурацыю для ўключэння DHCP-сервера", "original_response": "Першапачатковы адказ", "click_to_view_queries": "Націсніце, каб прагледзець запыты", - "port_53_faq_link": "Порт 53 часта заняты службамі \"DNSStubListener\" ці \"systemd-resolved\". Азнаёмцеся з <0>інструкцыяй пра тое, як гэта дазволіць.", + "port_53_faq_link": "Порт 53 часта заняты службамі «DNSStubListener» ці «systemd-resolved». Азнаёмцеся з <0>інструкцыяй пра тое, як гэта дазволіць.", "adg_will_drop_dns_queries": "AdGuard Home скіне ўсе DNS-запыты ад гэтага кліента.", "filter_allowlist": "УВАГА: Гэта дзеянне таксама выключыць правіла «{{disallowed_rule}}» са спіса дазволеных кліентаў.", "last_rule_in_allowlist": "Няможна заблакаваць гэтага кліента, бо вынятак правіла «{{disallowed_rule}}» АДКЛЮЧЫЦЬ рэжым белага спіса.", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index 8331dd50..a01e1c92 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -47,6 +47,7 @@ "form_error_server_name": "Neplatný název serveru", "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"", "form_error_positive": "Musí být větší než 0", + "form_error_gateway_ip": "Pronájem nemůže mít IP adresu brány", "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Musí být menší než začátek rozsahu", "greater_range_start_error": "Musí být větší než začátek rozsahu", @@ -85,7 +86,7 @@ "form_enter_hostname": "Zadejte název hostitele", "error_details": "Podrobnosti chyby", "response_details": "Detail odpovědi", - "request_details": "Detail požadavku", + "request_details": "Detaily požadavku", "client_details": "Detaily klienta", "details": "Detaily", "back": "Zpět", @@ -213,7 +214,7 @@ "example_upstream_udp": "obvyklý DNS (skrze UDP, název hostitele);", "example_upstream_dot": "šifrovaný <0>DNS skrze TLS;", "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS;", - "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC (experimentální);", + "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC;", "example_upstream_sdns": "<0>DNS razítka pro <1>DNSCrypt nebo <2>DNS skrze HTTPS řešitele;", "example_upstream_tcp": "obvyklý DNS (přes TCP);", "example_upstream_tcp_hostname": "obvyklý DNS (skrze TCP, název hostitele);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy", "dns_test_ok_toast": "Specifikované DNS servery pracují správně", "dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali", + "dns_test_warning_toast": "Upstream \"{{key}}\" neodpovídá na testovací požadavky a nemusí fungovat správně", "unblock": "Odblokovat", "block": "Blokovat", "disallow_this_client": "Blokovat tohoto klienta", @@ -362,15 +364,15 @@ "encryption_config_saved": "Konfigurace šifrování byla uložena", "encryption_server": "Název serveru", "encryption_server_enter": "Zadejte název domény", - "encryption_server_desc": "Abyste mohli používat HTTPS, musíte zadat název serveru, který odpovídá vašemu certifikátu SSL nebo zástupnému certifikátu. Pokud není pole nastaveno, bude přijímat připojení TLS pro libovolnou doménu.", + "encryption_server_desc": "Pokud je nastaveno, AdGuard Home detekuje ClientID, odpovídá na dotazy DDR a provádí další ověření připojení. Pokud není nastaveno, jsou tyto funkce vypnuty. Musí odpovídat jednomu z názvů DNS v certifikátu.", "encryption_redirect": "Automaticky přesměrovat na HTTPS", "encryption_redirect_desc": "Pokud je zaškrtnuto, AdGuard Home vás automaticky přesměruje z adres HTTP na HTTPS.", "encryption_https": "HTTPS port", "encryption_https_desc": "Pokud je nakonfigurován port HTTPS, AdGuard Home administrátorské rozhraní bude přístupné přes HTTPS a bude také poskytovat DNS skrze HTTPS na '/dns-query'.", "encryption_dot": "DNS skrze TLS port", "encryption_dot_desc": "Pokud je tento port nakonfigurován, AdGuard Home bude na tomto portu spouštět DNS skrze TLS server.", - "encryption_doq": "Port DNS skrze QUIC (experimentální)", - "encryption_doq_desc": "Pokud je tento port nakonfigurován, AdGuard Home spustí na tomto portu server DNS skrze QUIC. Je to experimentální a nemusí být spolehlivé. V současnosti také není příliš mnoho klientů, kteří to podporují.", + "encryption_doq": "Port DNS skrze QUIC", + "encryption_doq_desc": "Pokud je tento port nakonfigurován, AdGuard Home bude na tomto portu spouštět DNS skrze QUIC server.", "encryption_certificates": "Certifikáty", "encryption_certificates_desc": "Chcete-li používat šifrování, musíte pro svou doménu poskytnout platný řetězec certifikátů SSL. Certifikát můžete získat bezplatně na adrese <0>{{link}}, nebo jej můžete zakoupit od jednoho z důvěryhodných certifikačních úřadů.", "encryption_certificates_input": "Zde můžete nakopírovat/vložit certifikáty PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Blokované domény", "access_blocked_desc": "Nezaměňujte to s filtry. AdGuard Home zruší dotazy DNS odpovídající těmto doménám a tyto dotazy se neobjeví ani v protokolu dotazů. Zde můžete určit přesné názvy domén, zástupné znaky a pravidla filtrování URL adres, např. \"example.org\", \"*.example.org\" nebo \"||example.org^\".", "access_settings_saved": "Nastavení přístupu bylo úspěšně uloženo", - "updates_checked": "Aktualizace úspěšně zkontrolovány", + "updates_checked": "Nová verze AdGuard Home je k dispozici\n", "updates_version_equal": "AdGuard Home je aktuální", "check_updates_now": "Zkontrolovat aktualizace nyní", "dns_privacy": "Soukromí DNS", diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index 4b077cf8..32bec1e2 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -47,6 +47,7 @@ "form_error_server_name": "Ugyldigt servernavn", "form_error_subnet": "Undernet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"", "form_error_positive": "Skal være større end 0", + "form_error_gateway_ip": "Lease kan ikke have gatewayens IP-adresse", "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Skal være mindre end starten på området", "greater_range_start_error": "Skal være større end starten på ​​området", @@ -213,7 +214,7 @@ "example_upstream_udp": "almindelig DNS (over UDP, værtsnavn);", "example_upstream_dot": "krypteret <0>DNS-over-TLS", "example_upstream_doh": "krypteret <0>DNS-over-HTTPS", - "example_upstream_doq": "krypteret <0>DNS-over-QUIC(eksperimentel);", + "example_upstream_doq": "krypteret <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps til <1>DNSCrypt eller <2>DNS-over-HTTPS-opløsere;", "example_upstream_tcp": "almindelig DNS (over TCP)", "example_upstream_tcp_hostname": "almindelig DNS (over TCP, værtsnavn);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream-servere er gemt", "dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt", "dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt", + "dns_test_warning_toast": "Upstream \"{{key}}\" svarer ikke på testforespørgsler og fungerer muligvis ikke korrekt", "unblock": "Afblokering", "block": "Blokering", "disallow_this_client": "Afvis denne klient", @@ -362,15 +364,15 @@ "encryption_config_saved": "Krypteringsopsætning gemt", "encryption_server": "Servernavn", "encryption_server_enter": "Angiv dit domænenavn", - "encryption_server_desc": "For at kunne bruge HTTPS skal du angive det servernavn, der matcher dit SSL-certifikat eller wildcard-certifikat. Er feltet ikke er opsat, accepterer det TLS-forbindelser til ethvert domæne.", + "encryption_server_desc": "Hvis indstillet, registrerer AdGuard Home ClientID'er, svarer på DDR-forespørgsler og udfører yderligere forbindelsesvalideringer. Hvis ikke er indstillet, er disse funktioner deaktiveret. Skal matche et af DNS-navnene i certifikatet.", "encryption_redirect": "Omdirigér automatisk til HTTPS", "encryption_redirect_desc": "Hvis afkrydset, omdirigerer AdGuard Home dig automatisk fra HTTP- til HTTPS-adresser.", "encryption_https": "HTTPS-port", "encryption_https_desc": "Er HTTPS-porten opsat, vil AdGuard Home admin grænsefladen være tilgængelig via HTTPS, og den vil muliggøre DNS-over-HTTPS på '/dns-query' placeringen.", "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-TLS server på denne port.", - "encryption_doq": "DNS-over-QUIC port (eksperimentel)", - "encryption_doq_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-QUIC server på denne port. Den er eksperimentel og er måske ikke pålidelig. Derudover understøttes den pt. heller ikke af ret mange klienter.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-QUIC server på denne port. ", "encryption_certificates": "Certifikater", "encryption_certificates_desc": "For at kunne bruge kryptering skal du angive en gyldig SSL-certifikatkæde til dit domæne. Du kan få et gratis certifikat via <0>{{link}}, eller du kan købe det via en af de betroede Certifikatmyndigheder.", "encryption_certificates_input": "Kopiér/indsæt dine PEM-kodede certifikater hér.", @@ -445,7 +447,7 @@ "access_blocked_title": "Ikke tilladte domæner", "access_blocked_desc": "Ikke at forveksle med filtre. AdGuard Home dropper DNS-forespørgsler matchende disse domæner, ej heller vil forespørgslerne optræde i forespørgselsloggen. Der kan angives præcise domænenavne, jokertegn eller URL-filterregler, f.eks. \"eksempel.org\", \"*.eksempel.org\", \"||eksempel.org^\" eller tilsvarende.", "access_settings_saved": "Adgangsindstillinger gemt", - "updates_checked": "Opdateringstjek foretaget", + "updates_checked": "En ny version af AdGuard Home er tilgængelig\n", "updates_version_equal": "AdGuard Home er opdateret", "check_updates_now": "Søg efter opdateringer nu", "dns_privacy": "DNS-fortrolighed", diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 0fe6345d..a1e49718 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -47,6 +47,7 @@ "form_error_server_name": "Ungültiger Servername", "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“", "form_error_positive": "Muss größer als 0 sein", + "form_error_gateway_ip": "Lease kann nicht die IP-Adresse des Gateways haben", "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen", "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein", "greater_range_start_error": "Muss größer als der Bereichsbeginn sein", @@ -213,7 +214,7 @@ "example_upstream_udp": "normales DNS (über UDP, Hostname);", "example_upstream_dot": "verschlüsseltes <0>DNS-over-TLS;", "example_upstream_doh": "verschlüsseltes <0>DNS-over-HTTPS;", - "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC (experimentell);", + "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS-Stempel für <1>DNSCrypt oder <2>DNS-over-HTTPS Resolver;", "example_upstream_tcp": "reguläres DNS (over TCP);", "example_upstream_tcp_hostname": "normales DNS (über TCP, Hostname);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert", "dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß", "dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise", + "dns_test_warning_toast": "Upstream „{{key}}“ reagiert nicht auf Testanfragen und funktioniert möglicherweise nicht fehlerfrei", "unblock": "Entsperren", "block": "Sperren", "disallow_this_client": "Diesen Client sperren", @@ -362,15 +364,15 @@ "encryption_config_saved": "Verschlüsselungskonfiguration gespeichert", "encryption_server": "Servername", "encryption_server_enter": "Domain-Namen eingeben", - "encryption_server_desc": "Um HTTPS verwenden zu können, müssen Sie den Servernamen eingeben, der zu Ihrem SSL-Zertifikat passt.", + "encryption_server_desc": "Wenn diese Option aktiviert ist, erkennt AdGuard Home ClientIDs, antwortet auf DDR-Anfragen und führt zusätzliche Verbindungsüberprüfungen durch. Wenn sie nicht gesetzt ist, sind diese Funktionen deaktiviert. Muss mit einem der DNS-Namen im Zertifikat übereinstimmen.", "encryption_redirect": "Automatisch auf HTTPS umleiten", "encryption_redirect_desc": "Wenn aktiviert, leitet AdGuard Home Sie automatisch von HTTP- auf HTTPS-Adressen um.", "encryption_https": "HTTPS-Port", "encryption_https_desc": "Wenn der HTTPS-Port konfiguriert ist, ist die AdGuard Home-Administrationsschnittstelle über HTTPS zugänglich und bietet auch DNS-over-HTTPS am Server „/dns-query“.", "encryption_dot": "DNS-over-TLS", "encryption_dot_desc": "Wenn dieser Port konfiguriert ist, führt AdGuard Home auf diesem Port einen DNS-over-TLS-Server aus.", - "encryption_doq": "Port für DNS-over-QUIC (experimentell)", - "encryption_doq_desc": "Wenn dieser Port eingerichtet ist, wird AdGuard Home einen DNS-over-QUIC-Server auf diesem Port ausführen. Es ist experimentell und möglicherweise nicht zuverlässig. Außerdem gibt es im Moment nicht allzu viele Clients, die ihn unterstützen.", + "encryption_doq": "Port für DNS-over-QUIC", + "encryption_doq_desc": "Wenn dieser Port eingerichtet ist, wird AdGuard Home einen DNS-over-QUIC-Server auf diesem Port ausführen. ", "encryption_certificates": "Zertifikate", "encryption_certificates_desc": "Um die Verschlüsselung verwenden zu können, müssen Sie eine gültige SSL-Zertifikatskette für Ihre Domain angeben. Sie können ein kostenloses Zertifikat für <0>{{link}} erhalten oder es bei einer der vertrauenswürdigen Zertifizierungsstellen kaufen.", "encryption_certificates_input": "Kopieren Sie Ihre PEM-codierten Zertifikate und fügen Sie sie hier ein.", @@ -445,7 +447,7 @@ "access_blocked_title": "Nicht zugelassene Domains", "access_blocked_desc": "Verwechseln Sie dies nicht mit Filtern. AdGuard Home verwirft DNS-Abfragen, die mit diesen Domänen übereinstimmen, und diese Abfragen erscheinen nicht einmal im Abfrageprotokoll. Hier können Sie die genauen Domain-Namen, Wildcards und URL-Filter-Regeln angeben, z.B. 'beispiel.org', '*.beispiel.org' oder '||beispiel.org^'.", "access_settings_saved": "Zugriffseinstellungen erfolgreich gespeichert", - "updates_checked": "Erfolgreich auf Aktualisierungen geprüft", + "updates_checked": "Neue Version von AdGuard Home ist jetzt verfügbar", "updates_version_equal": "AdGuard Home ist aktuell", "check_updates_now": "Jetzt nach Aktualisierungen suchen", "dns_privacy": "DNS-Datenschutz", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index f39d2f51..ca423562 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -47,6 +47,7 @@ "form_error_server_name": "Invalid server name", "form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"", "form_error_positive": "Must be greater than 0", + "form_error_gateway_ip": "Lease can't have the IP address of the gateway", "out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Must be lower than range start", "greater_range_start_error": "Must be greater than range start", @@ -210,17 +211,20 @@ "example_comment_hash": "# Also a comment.", "example_regex_meaning": "block access to domains matching the specified regular expression.", "example_upstream_regular": "regular DNS (over UDP);", + "example_upstream_regular_port": "regular DNS (over UDP, with port);", "example_upstream_udp": "regular DNS (over UDP, hostname);", "example_upstream_dot": "encrypted <0>DNS-over-TLS;", "example_upstream_doh": "encrypted <0>DNS-over-HTTPS;", - "example_upstream_doq": "encrypted <0>DNS-over-QUIC (experimental);", + "example_upstream_doq": "encrypted <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps for <1>DNSCrypt or <2>DNS-over-HTTPS resolvers;", "example_upstream_tcp": "regular DNS (over TCP);", + "example_upstream_tcp_port": "regular DNS (over TCP, with port);", "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);", "all_lists_up_to_date_toast": "All lists are already up-to-date", "updated_upstream_dns_toast": "Upstream servers successfully saved", "dns_test_ok_toast": "Specified DNS servers are working correctly", "dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly", + "dns_test_warning_toast": "Upstream \"{{key}}\" does not respond to test requests and may not work properly", "unblock": "Unblock", "block": "Block", "disallow_this_client": "Disallow this client", @@ -362,15 +366,15 @@ "encryption_config_saved": "Encryption configuration saved", "encryption_server": "Server name", "encryption_server_enter": "Enter your domain name", - "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.", + "encryption_server_desc": "If set, AdGuard Home detects ClientIDs, responds to DDR queries, and performs additional connection validations. If not set, these features are disabled. Must match one of the DNS Names in the certificate.", "encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_https": "HTTPS port", "encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '/dns-query' location.", "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.", - "encryption_doq": "DNS-over-QUIC port (experimental)", - "encryption_doq_desc": "If this port is configured, AdGuard Home will run a DNS-over-QUIC server on this port. It's experimental and may not be reliable. Also, there are not too many clients that support it at the moment.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "If this port is configured, AdGuard Home will run a DNS-over-QUIC server on this port.", "encryption_certificates": "Certificates", "encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}} or you can buy it from one of the trusted Certificate Authorities.", "encryption_certificates_input": "Copy/paste your PEM-encoded certificates here.", @@ -445,7 +449,7 @@ "access_blocked_title": "Disallowed domains", "access_blocked_desc": "Not to be confused with filters. AdGuard Home drops DNS queries matching these domains, and these queries don't even appear in the query log. You can specify exact domain names, wildcards, or URL filter rules, e.g. \"example.org\", \"*.example.org\", or \"||example.org^\" correspondingly.", "access_settings_saved": "Access settings successfully saved", - "updates_checked": "Updates successfully checked", + "updates_checked": "A new version of AdGuard Home is available", "updates_version_equal": "AdGuard Home is up-to-date", "check_updates_now": "Check for updates now", "dns_privacy": "DNS Privacy", diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index e495c81d..895d0c33 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nombre de servidor no válido", "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"", "form_error_positive": "Debe ser mayor que 0", + "form_error_gateway_ip": "La asignación no puede tener la dirección IP de la puerta de enlace", "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Debe ser inferior que el inicio de rango", "greater_range_start_error": "Debe ser mayor que el inicio de rango", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS regular (mediante UDP, nombre del host).", "example_upstream_dot": "cifrado <0>DNS mediante TLS.", "example_upstream_doh": "cifrado <0>DNS mediante HTTPS.", - "example_upstream_doq": "cifrado <0>DNS mediante QUIC (experimental).", + "example_upstream_doq": "cifrado <0>DNS mediante QUIC.", "example_upstream_sdns": "<0>DNS Stamps para <1>DNSCrypt o resolutores <2>DNS mediante HTTPS.", "example_upstream_tcp": "DNS regular (mediante TCP).", "example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente", "dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente", "dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente", + "dns_test_warning_toast": "DNS de subida \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente", "unblock": "Desbloquear", "block": "Bloquear", "disallow_this_client": "No permitir a este cliente", @@ -362,15 +364,15 @@ "encryption_config_saved": "Configuración de cifrado guardado", "encryption_server": "Nombre del servidor", "encryption_server_enter": "Ingresa el nombre del dominio", - "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado comodín. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.", + "encryption_server_desc": "Si se configura, AdGuard Home detecta los ID de clientes, responde a las consultas DDR y realiza validaciones de conexión adicionales. Si no se configura, estas funciones se deshabilitarán. Debe coincidir con uno de los nombres DNS del certificado.", "encryption_redirect": "Redireccionar a HTTPS automáticamente", "encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.", "encryption_https": "Puerto HTTPS", "encryption_https_desc": "Si el puerto HTTPS está configurado, la interfaz de administración de AdGuard Home será accesible a través de HTTPS, y también proporcionará DNS mediante HTTPS en la ubicación '/dns-query'.", "encryption_dot": "Puerto DNS mediante TLS", "encryption_dot_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante TLS en este puerto.", - "encryption_doq": "Puerto DNS mediante QUIC (experimental)", - "encryption_doq_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante QUIC en este puerto. Es experimental y puede no ser confiable. Además, no hay muchos clientes que lo soporten por el momento.", + "encryption_doq": "Puerto DNS mediante QUIC", + "encryption_doq_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante QUIC en este puerto.", "encryption_certificates": "Certificados", "encryption_certificates_desc": "Para utilizar el cifrado, debes proporcionar una cadena de certificado SSL válida para tu dominio. Puedes obtener un certificado gratuito en <0>{{link}} o puedes comprarlo en una de las autoridades de certificación de confianza.", "encryption_certificates_input": "Copia/pega aquí tu certificado codificado PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Dominios no permitidos", "access_blocked_desc": "No debe confundirse con filtros. AdGuard Home descartará las consultas DNS que coincidan con estos dominios, y estas consultas ni siquiera aparecerán en el registro de consultas. Puedes especificar nombres de dominio exactos, comodines o reglas de filtrado de URL, por ejemplo: \"ejemplo.org\", \"*.ejemplo.org\" o \"||ejemplo.org^\" correspondientemente.", "access_settings_saved": "Configuración de acceso guardado correctamente", - "updates_checked": "Actualizaciones comprobadas correctamente", + "updates_checked": "La nueva versión de AdGuard Home está disponible", "updates_version_equal": "AdGuard Home está actualizado", "check_updates_now": "Buscar actualizaciones ahora", "dns_privacy": "DNS cifrado", diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json index 20cc372d..aef9e5d3 100644 --- a/client/src/__locales/fa.json +++ b/client/src/__locales/fa.json @@ -6,7 +6,6 @@ "bootstrap_dns": "خودراه انداز سرورهای DNS", "bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.", "local_ptr_title": "سرورهای خصوصی DNS", - "local_ptr_desc": "سرور یا سرور های DNS ای که AdGuard Home برای درخواست های منابع محلی ارائه شده مورد استفاده قرار خواهد داد. برای مثال، این سرور برای تعیین نام های سرویس دهنده برای سرویس گیرنده با آدرس های آی پی خصوصی مورد استفاده قرار خواهد گرفت. اگر تعیین نشود،AdGuard Home به طور خودکار از تعیین کننده ی DNS پیش فرض شما استفاده خواهد کرد.", "local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home نتوانست برای این دستگاه تعیین کننده های DNS معکوس محرمانه مناسب را معین کند.", "local_ptr_placeholder": "در هر خط یک آدرس سرور را وارد کنید", @@ -321,7 +320,6 @@ "install_devices_android_list_5": "گروه مقادیر DNS 1 و DNS 2 را به آدرس سرور AdGuard Home خود تغییر دهید.", "install_devices_ios_list_1": "از صفحه خانه،تنظیمات را فشار دهید.", "install_devices_ios_list_2": "وای فای را از منوی چپ انتخاب کنید (پیکربندی DNS دستی برای ارتباط موبایلی غیرممکن است).", - "install_devices_ios_list_3": "روی نام شبکه فعال فعلی کلیک کنید.", "install_devices_ios_list_4": "در فیلد DNS آدرس سرور AdGuard Home را وارد کنید", "get_started": "شروع به کار", "next": "بعدی", @@ -413,7 +411,7 @@ "access_blocked_title": "دامنه های مسدود شده", "access_blocked_desc": "این را با فیلتر ها به اشتباه نگیرید.AdGuard Home جستار DNS را با این دامنه ها در جستار سوال ها نمی پذیرد.", "access_settings_saved": "تنظیمات دسترسی با موفقیت ذخیره شد", - "updates_checked": "بروز رسانی با موفقیت بررسی شد", + "updates_checked": "نسخه جدیدی از AdGuard Home در دسترس است", "updates_version_equal": "AdGuard Home بروز است", "check_updates_now": "حالا بررسی برای بروز رسانی", "dns_privacy": "حریم خصوصی DNS", diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json index 3141b12a..197f74d3 100644 --- a/client/src/__locales/fi.json +++ b/client/src/__locales/fi.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS-palvelimet", "bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.", "local_ptr_title": "Yksityiset käänteiset DNS-palvelimet", - "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien päätelaitteiden osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, käyttää AdGuard Home käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.", + "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-kyselyille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-kyselyiden osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.", "local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisiä DNS-resolvereita: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteistä DNS-resolveria.", "local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Virheellinen IPv6-osoite", "form_error_ip_format": "Virheellinen IP-osoite", "form_error_mac_format": "Virheellinen MAC-osoite", - "form_error_client_id_format": "Päätelaitteen ID voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja", + "form_error_client_id_format": "ClientID-tunniste voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja", "form_error_server_name": "Virheellinen palvelimen nimi", "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"", "form_error_positive": "Oltava suurempi kuin 0", + "form_error_gateway_ip": "Lainalla ei voi olla yhdyskäytävän IP-osoitetta", "out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella", "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi", "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi", @@ -70,7 +71,7 @@ "dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta", "dhcp_static_ip_error": "Jotta DHCP-palvelinta voidaan käyttää, on määritettävä kiinteä IP-osoite. AdGuard Home ei voinut tunnistaa, onko tälle verkkosovittimelle määritetty IP-osoite kiinteä. Määritä kiinteä IP-osoite itse.", "dhcp_dynamic_ip_found": "Järjestelmäsi käyttää verkkosovittimelle <0>{{interfaceName}} dynaamista IP-osoitetta. Jotta voit käyttää DHCP-palvelinta, on sovittimelle määritettävä kiinteä IP-osoite. Nykyinen IP-osoitteesi on <0>{{ipAddress}}. Tämä osoite määritetään automaattisesti kiinteäksi, jos painat \"Ota DHCP-palvelin käyttöön\" -painiketta.", - "dhcp_lease_added": "Kiinteä laina \"{{key}}\" on lisätty", + "dhcp_lease_added": "Kiinteä laina \"{{key}}\" lisättiin", "dhcp_lease_deleted": "Kiinteä laina \"{{key}}\" poistettiin", "dhcp_new_static_lease": "Uusi kiinteä laina", "dhcp_static_leases_not_found": "Kiinteitä DHCP-lainoja ei löytynyt", @@ -213,7 +214,7 @@ "example_upstream_udp": "tavallinen DNS (UDP, isäntänimi);", "example_upstream_dot": "salattu <0>DNS-over-TLS;", "example_upstream_doh": "salattu <0>DNS-over-HTTPS;", - "example_upstream_doq": "salattu <0>DNS-over-QUIC (kokeellinen);", + "example_upstream_doq": "salattu <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamp -merkinnät <1>DNSCrypt tai <2>DNS-over-HTTPS -resolvereille;", "example_upstream_tcp": "tavallinen DNS (TCP:n välityksellä);", "example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin", "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein", "dns_test_not_ok_toast": "Palvelin \"{{key}}\": ei voitu käyttää, tarkista sen oikeinkirjoitus", + "dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla", "unblock": "Salli", "block": "Estä", "disallow_this_client": "Estä tämä päätelaite", @@ -277,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "Päätelaitteen ID", - "client_id_placeholder": "Syötä päätelaitteen ID", - "client_id_desc": "Päätelaitteet voidaan tunnistaa erityisillä ID-tunnisteilla. Lue lisää päätelaitteiden tunnistuksesta täältä.", + "client_id": "ClientID", + "client_id_placeholder": "Syötä ClientID", + "client_id_desc": "Päätelaitteet voidaan tunnistaa erityisillä ClientID-tunnisteilla. Lue lisää päätelaitteiden tunnistuksesta täältä.", "download_mobileconfig_doh": "Lataa .mobileconfig-tiedosto DNS-over-HTTPS -käytölle", "download_mobileconfig_dot": "Lataa .mobileconfig-tiedosto DNS-over-TLS -käytölle", "download_mobileconfig": "Lataa asetustiedosto", @@ -344,14 +346,14 @@ "install_devices_macos_list_2": "Paina \"Verkko\".", "install_devices_macos_list_3": "Valitse listan ensimmäinen yhteys ja paina \"Lisävalinnat\".", "install_devices_macos_list_4": "Valitse DNS-välilehti ja syötä AdGuard Home -palvelimesi osoitteet.", - "install_devices_android_list_1": "Napauta Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".", - "install_devices_android_list_2": "Napauta \"Yhteydet\" ja sitten \"Wi-Fi\". Näytetään kaikki käytettävissä olevat langattomat verkot (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).", - "install_devices_android_list_3": "Napauta yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".", - "install_devices_android_list_4": "Saatat joutua napauttamaan \"Lisäasetukset\" nähdäksesi lisää valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.", + "install_devices_android_list_1": "Paina Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".", + "install_devices_android_list_2": "Paina \"Yhteydet\" ja sitten \"Wi-Fi\". Kaikki käytettävissä olevat langattomat verkot näytetään (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).", + "install_devices_android_list_3": "Paina yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".", + "install_devices_android_list_4": "Saatat joutua painamaan \"Lisäasetukset\" nähdäksesi enemmän valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.", "install_devices_android_list_5": "Syötä \"DNS 1\" ja \"DNS 2\" -kenttiin AdGuard Home -palvelimesi osoitteet.", - "install_devices_ios_list_1": "Napauta aloitusnäytöstä \"Asetukset\".", + "install_devices_ios_list_1": "Paina aloitusnäytöstä \"Asetukset\".", "install_devices_ios_list_2": "Valitse vasemmalta \"Wi-Fi\" (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).", - "install_devices_ios_list_3": "Valitse yhdistetty verkko.", + "install_devices_ios_list_3": "Valitse tällä hetkellä aktiivinen verkko.", "install_devices_ios_list_4": "Syötä \"DNS\" -kenttään AdGuard Home -palvelimesi osoitteet.", "get_started": "Aloita", "next": "Seuraava", @@ -362,15 +364,15 @@ "encryption_config_saved": "Salausasetukset tallennettiin", "encryption_server": "Palvelimen nimi", "encryption_server_enter": "Syötä verkkotunnuksesi", - "encryption_server_desc": "HTTPS-yhteyden käyttöä varten, on syötettävä SSL- tai jokerivarmennetta vastaava palvelimen nimi. Jos kenttä on tyhjä, sallitaan kaikkien verkkotunnusten TLS-yhteydet.", + "encryption_server_desc": "Jos määritetty, AdGuard Home tunnistaa ClientID-tunnisteet, vastaa DDR-kyselyihin ja suorittaa yhteyden lisätarkistuksia. Jos ei määritetty, nämä ominaisuudet eivät ole käytössä. On vastattava yhtä varmenteen DNS-nimistä.", "encryption_redirect": "Automaattinen HTTPS-ohjaus", "encryption_redirect_desc": "Jos käytössä, AdGuard Home ohjaa HTTP-osoitteet automaattisesti HTTPS-osoitteisiin.", "encryption_https": "HTTPS-portti", "encryption_https_desc": "Jos HTTPS-portti on määritetty, on AdGuard Homen hallintapaneeli käytettävissä HTTPS-yhteydellä ja lisäksi tämä mahdollistaa myös DNS-over-HTTPS -yhteyden '/dns-query' -kohteessa.", "encryption_dot": "DNS-over-TLS -portti", "encryption_dot_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-TLS -palvelimen tässä portissa.", - "encryption_doq": "DNS-over-QUIC -portti (kokeellinen)", - "encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC -palvelimen tässä portissa. Ominaisuus on kokeellinen, eikä välttämättä luotettava. Lisäksi tätä tukevia päätelaitteita ei vielä ole kovin paljon.", + "encryption_doq": "DNS-over-QUIC-portti", + "encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC-palvelimen tässä portissa.", "encryption_certificates": "Varmenteet", "encryption_certificates_desc": "Salauksen käyttämiseksi, on syötettävä verkkotunnuksellesi myönnetty, aito SSL-varmenneketju. Voit hankkia ilmaisen varmenteen osoitteesta <0>{{link}} tai ostaa sellaisen joltakin luotetulta varmentajalta.", "encryption_certificates_input": "Kopioi/liitä PEM-koodatut varmenteesi tähän.", @@ -419,7 +421,7 @@ "client_edit": "Muokkaa päätelaitetta", "client_identifier": "Tunniste", "ip_address": "IP-osoite", - "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP- tai MAC-osoitteista, CIDR-merkinnöistä tai erityisistä päätelaite ID -tunnisteista (voidaan käyttää DoT/DoH/DoQ yhteydessä). Lue lisää päätelaitteiden tunnistuksesta <0>täältä.", + "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP- tai MAC-osoitteista, CIDR-merkinnöistä tai erityisistä ClientID-tunnisteista (voidaan käyttää DoT/DoH/DoQ yhteydessä). Lue lisää päätelaitteiden tunnistuksesta <0>täältä.", "form_enter_ip": "Syötä IP-osoite", "form_enter_subnet_ip": "Syötä aliverkossa \"{{cidr}}\" oleva IP-osoite", "form_enter_mac": "Syötä MAC-osoite", @@ -439,13 +441,13 @@ "access_title": "Käytön asetukset", "access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.", "access_allowed_title": "Sallitut päätelaitteet", - "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaite ID -tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.", + "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai ClientID-tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.", "access_disallowed_title": "Kielletyt päätelaitteet", - "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaite ID -tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.", + "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai ClientID-tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.", "access_blocked_title": "Kielletyt verkkotunnukset", "access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".", "access_settings_saved": "Käytön asetukset tallennettiin", - "updates_checked": "Päivitykset tarkastettiin", + "updates_checked": "Uusi versio AdGuard Home -ohjelmasta on saatavana\n", "updates_version_equal": "AdGuard Home on ajan tasalla", "check_updates_now": "Tarkista päivitykset nyt", "dns_privacy": "DNS-tietosuoja", diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index afbe91cd..fe891203 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nom de serveur invalide", "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »", "form_error_positive": "Doit être supérieur à 0", + "form_error_gateway_ip": "Le bail ne peut pas avoir d'adresse IP de la passerelle", "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} »", "lower_range_start_error": "Doit être inférieur au début de plage", "greater_range_start_error": "Doit être supérieur au début de plage", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS normal (sur UDP, nom d’hôte) ;", "example_upstream_dot": "<0>DNS-over-TLS chiffré ;", "example_upstream_doh": "<0>DNS-over-HTTPS chiffré ;", - "example_upstream_doq": "<0>DNS-over-QUIC chiffré (expérimental) ;", + "example_upstream_doq": "<0>DNS-over-QUIC chiffré;", "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps pour <1>DNSCrypt ou les résolveurs <2>DNS_over_HTTPS ;", "example_upstream_tcp": "DNS classique (au-dessus de TCP) ;", "example_upstream_tcp_hostname": "DNS normal (sur TCP, nom d’hôte) ;", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Serveurs en amont enregistrés", "dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement", "dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct", + "dns_test_warning_toast": "L'amont « {{key}} » ne répond pas aux demandes de test et peut ne pas fonctionner correctement.", "unblock": "Débloquer", "block": "Bloquer", "disallow_this_client": "Interdire ce client", @@ -336,7 +338,7 @@ "install_devices_router_list_4": "Vous ne pouvez pas définir un serveur DNS personnalisé sur certains types de routeurs. Dans ce cas, la configuration de AdGuard Home en tant que <0>serveur DHCP peut aider. Sinon, vous devez rechercher le manuel sur la façon de personnaliser les serveurs DNS pour votre modèle de routeur particulier.", "install_devices_windows_list_1": "Ouvrez votre Panneau de configuration depuis le menu Démarrer ou la recherche Windows.", "install_devices_windows_list_2": "Allez dans la catégorie Réseau et Internet et ensuite dans le Centre Réseau et Partage.", - "install_devices_windows_list_3": "Dans le panneau de gauche, cliquez sur \"Modifier les paramètres de l'adaptateur\".", + "install_devices_windows_list_3": "Dans le panneau de gauche, cliquez sur « Modifier les paramètres de l'adaptateur ».", "install_devices_windows_list_4": "Cliquez avec le bouton droit de la souris sur votre connexion active et sélectionnez Propriétés.", "install_devices_windows_list_5": "Recherchez « Protocole Internet Version 4 (TCP/IPv4) » (soit, pour IPv6, « Protocole Internet Version 6 (TCP/IPv6) ») dans la liste, sélectionnez-la puis cliquez à nouveau sur Propriétés.", "install_devices_windows_list_6": "Sélectionnez « Utiliser l’adresse de serveur DNS suivante » et saisissez votre adresse de serveur AdGuard Home.", @@ -362,15 +364,15 @@ "encryption_config_saved": "Configuration de chiffrement enregistrée", "encryption_server": "Nom du serveur", "encryption_server_enter": "Entrez votre nom de domaine", - "encryption_server_desc": "Pour utiliser HTTPS, vous devez saisir le nom du serveur qui correspond à votre certificat SSL ou wildcard. Si le champ n'est pas configuré, les connexions TLS pour tous les domaines seront acceptées.", + "encryption_server_desc": "Si cette option est définie, AdGuard Home détecte les ClientID, répond aux requêtes DDR et effectue des validations de connexion supplémentaires. Si elle n'est pas définie, ces fonctions sont désactivées. Doit correspondre à l'un des noms DNS du certificat.", "encryption_redirect": "Redirection automatiquement vers HTTPS", "encryption_redirect_desc": "Si coché, AdGuard Home vous redirigera automatiquement d'adresses HTTP vers HTTPS.", "encryption_https": "Port HTTPS", "encryption_https_desc": "Si le port HTTPS est configuré, l'interface administrateur de AdGuard Home sera accessible via HTTPS et fournira aussi un service DNS-over-HTTPS sur l'emplacement '/dns-query'.", "encryption_dot": "Port DNS-over-TLS", "encryption_dot_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS-over-TLS sur ce port.", - "encryption_doq": "Port DNS sur QUIC (expérimental)", - "encryption_doq_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS sur QUIC sur ce port. Ceci est expérimental et possiblement pas entièrement fiable. Peu de clients le prennent en charge actuellement.", + "encryption_doq": "Port DNS sur QUIC", + "encryption_doq_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS sur QUIC sur ce port. ", "encryption_certificates": "Certificats", "encryption_certificates_desc": "Pour utiliser le chiffrement, vous devez fournir une chaîne de certificats SSL valide pour votre domaine. Vous pouvez en obtenir une gratuitement sur <0>{{link}} ou vous pouvez en acheter une via les Autorités de Certification de confiance.", "encryption_certificates_input": "Copiez/coller vos certificats encodés PEM ici.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domaines interdits", "access_blocked_desc": "A ne pas confondre avec les filtres. AdGuard Home rejette les requêtes DNS correspondant à ces domaines, et ces requêtes n'apparaissent même pas dans le journal des requêtes. Vous pouvez spécifier des noms de domaine exacts, des caractères génériques ou des règles de filtrage d'URL, par exemple « exemple.org », « *.exemple.org » ou « ||example.org^ » de manière correspondante.", "access_settings_saved": "Paramètres d'accès enregistrés avec succès", - "updates_checked": "Mises à jour vérifiées", + "updates_checked": "Une nouvelle version de AdGuard Home est disponible", "updates_version_equal": "AdGuard Home est à jour", "check_updates_now": "Vérifier les mises à jour", "dns_privacy": "Confidentialité DNS", diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index 47f2296c..7b9d5453 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nevažeće ime poslužitelja", "form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"", "form_error_positive": "Mora biti veće od 0", + "form_error_gateway_ip": "Najam ne može imati IP adresu pristupnika", "out_of_range_error": "Mora biti izvan ranga \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Mora biti niže od početnog ranga", "greater_range_start_error": "Mora biti veće od krajnjeg ranga", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni", "dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni", "dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali", + "dns_test_warning_toast": "Upstream \"{{key}}\" ne odgovara na zahtjeve za testiranje i možda neće raditi ispravno", "unblock": "Odblokiraj", "block": "Blokiraj", "disallow_this_client": "Onemogući ovog klijenta", @@ -362,7 +364,7 @@ "encryption_config_saved": "Konfiguracija šifriranja spremljena", "encryption_server": "Naziv poslužitelja", "encryption_server_enter": "Unesite naziv domene", - "encryption_server_desc": "Da biste koristili HTTPS, morate unijeti ime poslužitelja koje odgovara vašem SSL certifikatu ili wildcard certifikatu. Ako polje nije postavljeno, prihvatit će TLS veze za bilo koju domenu.", + "encryption_server_desc": "Ako je postavljeno, AdGuard Home otkriva ClientID-ove, odgovara na DDR upite i provodi dodatne provjere valjanosti veze. Ako nije postavljeno, ove značajke su onemogućene. Mora odgovarati jednom od DNS naziva u certifikatu.", "encryption_redirect": "Automatski preusmjeri na HTTPS", "encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.", "encryption_https": "HTTPS port", @@ -370,7 +372,7 @@ "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "Ako je ovaj port postavljen, AdGuard Home će pokrenuti DNS-over-TLS poslužitelj na ovom portu.", "encryption_doq": "DNS-over-QUIC port (eksperimentalno)", - "encryption_doq_desc": "Ako je ovaj port postavljen, AdGuard Home će na ovom portu pokrenuti DNS-over-QUIC poslužitelj. Eksperimentalno je i možda nije pouzdano. Također, trenutno nema previše klijenata koji to podržavaju.", + "encryption_doq_desc": "Ako je ovaj priključak konfiguriran, AdGuard Home će na ovom priključku pokretati DNS-over-QUIC poslužitelj.", "encryption_certificates": "Certifikati", "encryption_certificates_desc": "Da biste koristili šifriranje, za svoju domenu morate osigurati važeći lanac SSL certifikata. Besplatan certifikat možete dobiti na <0>{{link}} ili ga možete kupiti od jednog od pouzdanih izdavatelja certifikata.", "encryption_certificates_input": "Zalijepite svoje PEM-kodirane certifikate ovdje.", @@ -445,7 +447,7 @@ "access_blocked_title": "Nedopuštene domene", "access_blocked_desc": "Ne smije se miješati s filterima. AdGuard Home ispušta DNS upite koji odgovaraju tim domenama, a ti se upiti čak i ne pojavljuju u zapisniku upita. Možete navesti točne nazive domena, zamjenske znakove ili pravila filtriranja URL-a, npr || example.org example.org. example.org^\" u skladu s tim.", "access_settings_saved": "Postavke pristupa su uspješno spremljene", - "updates_checked": "Uspješna provjera ažuriranja", + "updates_checked": "Dostupna je nova verzija AdGuard Home-a", "updates_version_equal": "AdGuard Home je ažuriran", "check_updates_now": "Provjeri ažuriranja sada", "dns_privacy": "DNS privatnost", @@ -511,7 +513,7 @@ "statistics_configuration": "Postavke statistike", "statistics_retention": "Spremanje statistike", "statistics_retention_desc": "Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni", - "statistics_clear": " Poništi statistiku", + "statistics_clear": "Poništi statistiku", "statistics_clear_confirm": "Jeste li sigurni da želite poništiti statistiku?", "statistics_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje statistike? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni", "statistics_cleared": "Statistika je uspješno uklonjenja", diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json index a47d069c..ffdbf7ce 100644 --- a/client/src/__locales/hu.json +++ b/client/src/__locales/hu.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS kiszolgálók", "bootstrap_dns_desc": "A Bootstrap DNS szerverek a DoH/DoT feloldók IP-címeinek feloldására szolgálnak.", "local_ptr_title": "Privát DNS szerverek", - "local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. Ezeket a szervereket arra használjuk, hogy reverse DNS által feloldjuk a kliensek hosztneveit privát IP címekre, például \"192.168.12.34\". Ha nincs beállítva, akkor az AdGuard Home, kivéve az ő saját címét, az operációs rendszer alapértelmezett DNS feloldók címeit fogja használni.", + "local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. ", "local_ptr_default_resolver": "Alapesetben az AdGuard Home a következő reverse DNS feloldókat használja: {{ip}}.", "local_ptr_no_default_resolver": "Az AdGuard Home nem tudta meghatározni a privát reverse DNS feloldókat ehhez a rendszerhez.", "local_ptr_placeholder": "Adjon meg soronként egy kiszolgáló címet", @@ -47,6 +47,7 @@ "form_error_server_name": "Érvénytelen szervernév", "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet", "form_error_positive": "0-nál nagyobbnak kell lennie", + "form_error_gateway_ip": "A bérleti szerződés nem tartalmazhatja az átjáró IP-címét", "out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete", "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete", @@ -213,7 +214,7 @@ "example_upstream_udp": "normál DNS (UDP felett, hostnév);", "example_upstream_dot": "titkosított <0>DNS-over-TLS;", "example_upstream_doh": "titkosított <0>DNS-over-HTTPS;", - "example_upstream_doq": "titkosított <0>DNS-over-QUIC (kísérleti);", + "example_upstream_doq": "titkosított <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps a <1>DNSCrypt vagy <2>DNS-over-HTTPS feloldókhoz;", "example_upstream_tcp": "hagyományos DNS (TCP felett);", "example_upstream_tcp_hostname": "normál DNS (TCP felett, hostnév);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve", "dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek", "dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be", + "dns_test_warning_toast": "A \"{{key}}\" feltöltés nem válaszol a tesztkérelmekre, és lehet, hogy nem működik megfelelően", "unblock": "Feloldás", "block": "Blokkolás", "disallow_this_client": "Tiltás ennek a kliensnek", @@ -362,15 +364,15 @@ "encryption_config_saved": "Titkosítási beállítások mentve", "encryption_server": "Szerver neve", "encryption_server_enter": "Adja meg az Ön domain címét", - "encryption_server_desc": "A HTTPS használatához meg kell adnia a szerver nevét, amely megegyezik az SSL tanúsítvánnyal vagy a helyettesítő tanúsítvánnyal. Ha a mező nincs beállítva, akkor bármely tartományhoz elfogadja a TLS kapcsolatokat.", + "encryption_server_desc": "Ha be van állítva, az AdGuard Home észleli az ClientID-ket, válaszol a DDR-lekérdezésekre, és további kapcsolatellenőrzéseket végez. Ha nincs beállítva, ezek a funkciók le vannak tiltva. Meg kell egyeznie a tanúsítványban szereplő DNS-nevek egyikével.", "encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra", "encryption_redirect_desc": "Ha be van jelölve, az AdGuard Home automatikusan átirányítja a HTTP kapcsolatokat a biztonságos HTTPS protokollra.", "encryption_https": "HTTPS port", "encryption_https_desc": "Ha a HTTPS port konfigurálva van, akkor az AdGuard Home admin felülete elérhető lesz a HTTPS-en keresztül, és ezenkívül DNS-over-HTTPS-t is biztosít a '/dns-query' helyen.", "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "Ha ez a port be van állítva, az AdGuard Home DNS-over-TLS szerverként tud futni ezen a porton.", - "encryption_doq": "DNS-over-QUIC port (kísérleti)", - "encryption_doq_desc": "Ha ez a port be van állítva, akkor az AdGuard Home egy DNS-over-QUIC szerverként fog futni ezen a porton. Ez egy kísérleti funkció és nem biztos, hogy megbízható. Emellett nincs sok olyan kliens, ami támogatná ezt jelenleg.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "Ha ez a port be van állítva, akkor az AdGuard Home egy DNS-over-QUIC szerverként fog futni ezen a porton. ", "encryption_certificates": "Tanúsítványok", "encryption_certificates_desc": "A titkosítás használatához érvényes SSL tanúsítványláncot kell megadnia a domainjéhez. Ingyenes tanúsítványt kaphat a <0>{{link}} webhelyen, vagy megvásárolhatja az egyik megbízható tanúsítványkibocsátó hatóságtól.", "encryption_certificates_input": "Másolja be ide a PEM-kódolt tanúsítványt.", @@ -445,7 +447,7 @@ "access_blocked_title": "Nem engedélyezett domainek", "access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel megegyezik, és ezek a lekérések nem is fognak megjelenni a lekérdezési naplóban sem. Megadhatja a pontos domain neveket, a helyettesítő karaktereket vagy az URL szűrési szabályokat, pl. ennek megfelelően \"example.org\", \"*.example.org\", vagy \"||example.org^\".", "access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek", - "updates_checked": "A frissítések sikeresen ellenőrizve lettek", + "updates_checked": "Elérhető az AdGuard Home új verziója", "updates_version_equal": "Az AdGuard Home naprakész", "check_updates_now": "Frissítések ellenőrzése most", "dns_privacy": "DNS Adatvédelem", diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json index 75e87b0d..0b055f31 100644 --- a/client/src/__locales/id.json +++ b/client/src/__locales/id.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nama server tidak valid", "form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"", "form_error_positive": "Harus lebih dari 0", + "form_error_gateway_ip": "Sewa tidak dapat memiliki alamat IP gateway", "out_of_range_error": "Harus di luar rentang \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Harus lebih rendah dari rentang awal", "greater_range_start_error": "Harus lebih besar dari rentang awal", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS biasa (lebih dari UDP, nama host);", "example_upstream_dot": "terenkripsi <0>DNS-over-TLS;", "example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS;", - "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC (eksperimental);", + "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>Stempel DNS untuk <1>DNSCrypt atau pengarah <2>DNS-over-HTTPS;", "example_upstream_tcp": "DNS reguler (melalui TCP);", "example_upstream_tcp_hostname": "DNS biasa (lebih dari TCP, nama host);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Server upstream berhasil disimpan", "dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar", "dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar", + "dns_test_warning_toast": "Upstream \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan baik", "unblock": "Buka Blokir", "block": "Blok", "disallow_this_client": "Cabut ijin untuk klien ini", @@ -362,15 +364,15 @@ "encryption_config_saved": "Pengaturan enkripsi telah tersimpan", "encryption_server": "Nama server", "encryption_server_enter": "Masukkan nama domain anda", - "encryption_server_desc": "Untuk menggunakan HTTPS, Anda harus memasukkan nama server yang cocok dengan sertifikat SSL Anda. Bila ruas tak ditata, akan menerima koneksi TLS bagi domain manapun.", + "encryption_server_desc": "Jika disetel, AdGuard Home mendeteksi ClientID, merespons kueri DDR, dan melakukan validasi koneksi tambahan. Jika tidak disetel, fitur-fitur ini dinonaktifkan. Harus cocok dengan salah satu Nama DNS dalam sertifikat.", "encryption_redirect": "Alihkan ke HTTPS secara otomatis", "encryption_redirect_desc": "Jika dicentang, AdGuard Home akan secara otomatis mengarahkan anda dari HTTP ke alamat HTTPS.", "encryption_https": "Port HTTPS", "encryption_https_desc": "Jika port HTTPS dikonfigurasi, antarmuka admin Home AdGuard akan dapat diakses melalui HTTPS, dan itu juga akan memberikan DNS-over-HTTPS di lokasi '/ dns-query'.", "encryption_dot": "Port DNS-over-TLS", "encryption_dot_desc": "Jika port ini terkonfigurasi, AdGuard Home akan menjalankan server DNS-over-TLS dalam port ini", - "encryption_doq": "Port DNS-over-QUIC (eksperimental)", - "encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini. Ini adalah eksperimental dan mungkin tidak dapat diandalkan. Juga, tidak banyak klien yang mendukungnya saat ini.", + "encryption_doq": "Port DNS-over-QUIC ", + "encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini.", "encryption_certificates": "Sertifikat", "encryption_certificates_desc": "Untuk menggunakan enkripsi, Anda perlu memberikan rantai sertifikat SSL yang valid untuk domain Anda. Anda bisa mendapatkan sertifikat gratis di <0>{{link}} atau Anda dapat membelinya dari salah satu Otoritas Sertifikat tepercaya.", "encryption_certificates_input": "Salin / rekatkan sertifikat PEM yang disandikan di sini.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domain yang diblokir", "access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.", "access_settings_saved": "Pengaturan akses berhasil disimpan", - "updates_checked": "Pembaruan berhasil dicek", + "updates_checked": "Versi baru AdGuard Home tersedia\n", "updates_version_equal": "AdGuard Home sudah tebaru", "check_updates_now": "Periksa pembaruan sekarang", "dns_privacy": "DNS Privasi", diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index cd499740..9a4ed74b 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nome server non valido", "form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"", "form_error_positive": "Deve essere maggiore di 0", + "form_error_gateway_ip": "Il leasing non può avere l'indirizzo IP del gateway", "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio", "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS regolare (over UDP, nome host);", "example_upstream_dot": "<0>DNS su TLS crittografato;", "example_upstream_doh": "<0>DNS su HTTPS crittografato;", - "example_upstream_doq": "<0>DNS su QUIC crittografato (sperimentale);", + "example_upstream_doq": "<0>DNS su QUIC crittografato;", "example_upstream_sdns": "<0>DNS Stamps per <1>DNSCrypt oppure i risolutori <2>DNS su HTTPS;", "example_upstream_tcp": "DNS regolare (over TCP);", "example_upstream_tcp_hostname": "DNS regolare (over TCP, nome host);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente", "dns_test_ok_toast": "I server DNS specificati funzionano correttamente", "dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente", + "dns_test_warning_toast": "Upstream \"{{key}}\" non risponde alle richieste di test e potrebbe non funzionare correttamente", "unblock": "Sblocca", "block": "Blocca", "disallow_this_client": "Blocca questo client", @@ -362,15 +364,15 @@ "encryption_config_saved": "Configurazione crittografia salvata", "encryption_server": "Nome server", "encryption_server_enter": "Inserisci il tuo nome di dominio", - "encryption_server_desc": "Per utilizzare HTTPS, è necessario immettere il nome del server che corrisponde al certificato SSL o al certificato wildcard. Se il campo risulterà vuoto, accetterà connessioni TLS per qualsiasi dominio.", + "encryption_server_desc": "Se impostato, AdGuard Home rileva i ClientID, risponde alle query DDR ed esegue ulteriori convalide della connessione. Se non sono impostate, queste funzioni sono disabilitate. Deve corrispondere a uno dei nomi DNS nel certificato.", "encryption_redirect": "Reindirizza automaticamente a HTTPS", "encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.", "encryption_https": "Porta HTTPS", "encryption_https_desc": "Se la porta HTTPS è configurata, l'interfaccia di amministrazione di AdGuard Home sarà accessibile tramite HTTPS e fornirà anche DNS su HTTPS nella posizione \"/ dns-query\".", "encryption_dot": "DNS su porta TLS", "encryption_dot_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su TLS su questa porta.", - "encryption_doq": "Porta DNS su QUIC (sperimentale)", - "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. Questa opzione è sperimentale e potrebbe non risultare affidabile. Inoltre, al momento non sono molti i client a supportarla.", + "encryption_doq": "Porta DNS su QUIC", + "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. ", "encryption_certificates": "Certificati", "encryption_certificates_desc": "Per utilizzare la crittografia, è necessario fornire una catena di certificati SSL valida per il proprio dominio. Puoi ottenere un certificato gratuito su <0> {{link}} o puoi acquistarlo da una delle Autorità di certificazione attendibili.", "encryption_certificates_input": "Copia / incolla qui i certificati codificati PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domini bloccati", "access_blocked_desc": "Da non confondere con i filtri. AdGuard Home eliminerà le richieste DNS corrispondenti a questi domini e queste richieste non verranno visualizzate nel relativo registro. Puoi specificare nomi di dominio esatti, caratteri jolly o regole di filtraggio URL, ad esempio \"esempio.org\", \"*.esempio.org\" o \"||esempio.org^\".", "access_settings_saved": "Impostazioni di accesso salvate correttamente", - "updates_checked": "Verifica aggiornamenti riuscita", + "updates_checked": "Nuova versione di AdGuard Home è disponibile", "updates_version_equal": "AdGuard Home è aggiornato", "check_updates_now": "Ricerca aggiornamenti ora", "dns_privacy": "Privacy DNS", diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index 0cd45548..fed72b6d 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -47,6 +47,7 @@ "form_error_server_name": "サーバー名が無効です", "form_error_subnet": "IPアドレス「{{ip}}」がサブネット「{{cidr}}」に含まれていません", "form_error_positive": "0より大きい値でなければなりません", + "form_error_gateway_ip": "リースはゲートウェイのIPアドレスになっていることができません", "out_of_range_error": "\"{{start}}\"〜\"{{end}}\" の範囲外である必要があります", "lower_range_start_error": "範囲開始よりも低い値である必要があります", "greater_range_start_error": "範囲開始値より大きい値でなければなりません", @@ -213,7 +214,7 @@ "example_upstream_udp": "通常のDNS(over UDP, ホスト名)。", "example_upstream_dot": "暗号化されている <0>DNS-over-TLS。", "example_upstream_doh": "暗号化されている <0>DNS-over-HTTPS。", - "example_upstream_doq": "暗号化 <0>DNS-over-QUIC(実験的)。", + "example_upstream_doq": "暗号化 <0>DNS-over-QUIC。", "example_upstream_sdns": "<1>DNSCrypt または <2>DNS-over-HTTPS リゾルバのための <0>DNS Stamps。", "example_upstream_tcp": "通常のDNS(over TCP)。", "example_upstream_tcp_hostname": "通常のDNS(over TCP, ホスト名)。", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "上流DNSサーバを保存しました。", "dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています", "dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください", + "dns_test_warning_toast": "アップストリーム\"{{key}}\"はテストリクエストに応答せず、正しく動作しない可能性があります。", "unblock": "ブロック解除", "block": "ブロック", "disallow_this_client": "このクライアントを拒否する", @@ -360,17 +362,17 @@ "encryption_title": "暗号化", "encryption_desc": "DNSと管理者ウェブインターフェースの両方に対する暗号化(HTTPS/QUIC/TLS)サポート。", "encryption_config_saved": "暗号化構成が保存されました。", - "encryption_server": "サーバ名", + "encryption_server": "サーバー名", "encryption_server_enter": "ドメイン名を入力してください", - "encryption_server_desc": "HTTPSを使用するには、SSL証明書またはワイルドカード証明書と一致するサーバー名を入力する必要があります。このフィールドが設定されていない場合は、任意のドメインのTLS接続を受け入れます。", + "encryption_server_desc": "こちらでサーバー名を設定すると、AdGuard HomeはClientIDを検出し、DDRクエリに応答し、追加の接続検証を実行します。設定されていない場合、これらの機能は無効になります。※証明書のDNS名のいずれかに一致する必要があります。", "encryption_redirect": "HTTPSに自動的にリダイレクト", "encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。", "encryption_https": "HTTPS ポート", "encryption_https_desc": "HTTPSポートが設定されていると、AdGuard Home 管理インターフェースはHTTPS経由でアクセス可能になり、そして「/dns-query」の場所にDNS-over-HTTPSも提供されます。", "encryption_dot": "DNS-over-TLS ポート", "encryption_dot_desc": "このポートが設定されていると、AdGuard HomeはこのポートでDNS-over-TLSサーバを実行します。", - "encryption_doq": "DNS-over-QUIC ポート (実験的)", - "encryption_doq_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。これは実験的なものであり、頼りにならない可能性があります。また、現時点ではこのサーバーをサポートするクライアントも少ないです。", + "encryption_doq": "DNS-over-QUIC ポート", + "encryption_doq_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。", "encryption_certificates": "証明書", "encryption_certificates_desc": "暗号化を使用するには、ドメインに有効なSSL証明書チェーンを提供する必要があります。無料の証明書は<0> {{link}} で入手できます。または、信頼できる認証局のいずれかから購入することもできます。", "encryption_certificates_input": "ここにPEM形式の証明書をコピー/ペーストしてください。", @@ -445,7 +447,7 @@ "access_blocked_title": "拒否するドメイン", "access_blocked_desc": "こちらをフィルタと混同しないでください。AdGuard Homeは、ここで入力されたドメインに一致するDNSクエリをドロップし、そういったクエリはクエリログにも表示されません。ここでは、「example.org」、「*.example.org」、「 ||example.org^ 」など、特定のドメイン名、ワイルドカード、URLフィルタルールを入力できます。", "access_settings_saved": "アクセス設定の保存に成功しました", - "updates_checked": "アップデートの確認に成功しました", + "updates_checked": "AdGuard Homeの新バージョンが利用可能です。", "updates_version_equal": "AdGuard Homeは既に最新です", "check_updates_now": "今すぐアップデートを確認する", "dns_privacy": "DNSプライバシー", diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index 19440d13..62080721 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -47,6 +47,7 @@ "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": "범위 시작보다 큰 값이어야 합니다", @@ -213,7 +214,7 @@ "example_upstream_udp": "일반 DNS (UDP를 통한, 호스트명);", "example_upstream_dot": "암호화된 <0>DNS-over-TLS;", "example_upstream_doh": "암호화된 <0>DNS-over-HTTPS;", - "example_upstream_doq": "암호화된 <0>DNS-over-QUIC (실험);", + "example_upstream_doq": "암호화된 <0>DNS-over-QUIC;", "example_upstream_sdns": "<1>DNSCrypt 또는 <2>DNS-over-HTTPS 리졸버를 위한 <0>DNS 스탬프;", "example_upstream_tcp": "일반 DNS (TCP를 통한 접속);", "example_upstream_tcp_hostname": "일반 DNS (TCP를 통한, 호스트명);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다", "dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다", "dns_test_not_ok_toast": "서버 \"{{key}}\": 사용할 수 없습니다, 제대로 작성했는지 확인하세요.", + "dns_test_warning_toast": "업스트림 '{{key}}'이(가) 테스트 요청에 응답하지 않으며 제대로 작동하지 않을 수 있습니다", "unblock": "차단 해제", "block": "차단", "disallow_this_client": "클라이언트 거부", @@ -362,15 +364,15 @@ "encryption_config_saved": "암호화 구성이 저장되었습니다", "encryption_server": "서버 이름", "encryption_server_enter": "도메인 이름을 입력하세요.", - "encryption_server_desc": "HTTPS를 사용하려면 SSL 인증서와 일치하는 서버 이름을 입력해야 합니다.", + "encryption_server_desc": "설정된 경우 AdGuard Home은 ClientID를 감지하고 DDR 쿼리에 응답하고 추가 연결 유효성 검사를 수행합니다. 설정하지 않으면 이러한 기능이 비활성화됩니다. 인증서의 DNS 이름 중 하나와 일치해야 합니다.", "encryption_redirect": "HTTPS로 자동 리디렉션", "encryption_redirect_desc": "상자를 체크하면 AdGuard Home 자동으로 사용자를 HTTP에서 HTTPS 주소로 리디렉션합니다.", "encryption_https": "HTTP 포트", "encryption_https_desc": "HTTPS 포트가 구성되면 HTTPS를 통해 AdGuard Home 관리자 인터페이스에 액세스할 수 있으며, '/dns-query' 위치에 DNS-over-HTTPS도 제공합니다.", "encryption_dot": "DNS-over-TLS 포트", "encryption_dot_desc": "이 포트가 구성된 경우 AdGuard Home 이 포트에서 DNS-over-TLS 서버를 실행합니다.", - "encryption_doq": "DNS-over-QUIC 포트 (실험)", - "encryption_doq_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. 이것은 실험적이며 신뢰할 수 없습니다. 또한 현재 이를 지원하는 클라이언트가 많지 않습니다.", + "encryption_doq": "DNS-over-QUIC 포트", + "encryption_doq_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. ", "encryption_certificates": "인증서", "encryption_certificates_desc": "암호화를 사용하려면 도메인에 대해 올바른 SSL 인증서 체인을 제공해야 합니다. <0>{{link}}에서 무료 증명서를 받을 수도 있고, 신뢰할 수있는 인증 기관에서 구입할 수 있습니다.", "encryption_certificates_input": "PEM으로 인코딩된 인증서 여기에 복사/붙여넣기하세요.", @@ -445,7 +447,7 @@ "access_blocked_title": "차단된 도메인", "access_blocked_desc": "이 기능을 필터와 혼동하지 마세요. AdGuard Home은 이 도메인에 대한 DNS 요청을 무시합니다. 여기에서는 'example.org' '*. example.org', '|| example.org ^'와 같은 특정 도메인 이름, 와일드 카드, URL 필터 규칙을 지정할 수 있습니다.", "access_settings_saved": "액세스 설정이 성공적으로 저장되었습니다.", - "updates_checked": "업데이트가 성공적으로 확인되었습니다", + "updates_checked": "AdGuard Home의 새 버전을 사용할 수 있습니다", "updates_version_equal": "AdGuard Home 최신 상태입니다.", "check_updates_now": "지금 업데이트 확인", "dns_privacy": "DNS 프라이버시", diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index 83278ee3..408dc561 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -47,6 +47,7 @@ "form_error_server_name": "Ongeldige servernaam", "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”", "form_error_positive": "Moet groter zijn dan 0", + "form_error_gateway_ip": "Lease kan niet het IP-adres van de gateway hebben", "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Moet lager zijn dan begin reeks", "greater_range_start_error": "Moet groter zijn dan begin reeks", @@ -213,7 +214,7 @@ "example_upstream_udp": "standaard DNS (via UDP, hostnaam);", "example_upstream_dot": "versleutelde <0>DNS-via-TLS;", "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS;", - "example_upstream_doq": "versleutelde <0>DNS-via-QUIC (experimenteel);", + "example_upstream_doq": "versleutelde <0>DNS-via-QUIC;", "example_upstream_sdns": "<0>DNS Stamps voor <1>DNSCrypt of <2>DNS-via-HTTPS oplossingen;", "example_upstream_tcp": "standaard DNS (over TCP);", "example_upstream_tcp_hostname": "standaard DNS (via TCP, hostnaam);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen", "dns_test_ok_toast": "Opgegeven DNS-servers werken correct", "dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven", + "dns_test_warning_toast": "Upstream \"{{key}}\" reageert niet op testverzoeken en werkt mogelijk niet goed", "unblock": "Deblokkeren", "block": "Blokkeren", "disallow_this_client": "Toepassing/systeem niet toelaten", @@ -362,15 +364,15 @@ "encryption_config_saved": "Versleuteling configuratie opgeslagen", "encryption_server": "Server naam", "encryption_server_enter": "Voer domein naam in", - "encryption_server_desc": "Om HTTPS te gebruiken, moet je de servernaam invoeren die overeenkomt met je SSL-certificaat of jokerteken-certificaat. Als het veld niet is ingesteld, accepteert het TLS-verbindingen voor elk domein.", + "encryption_server_desc": "Indien ingesteld, detecteert AdGuard Home Client-ID's, reageert op DDR-zoekopdrachten en voert aanvullende verbindingsvalidaties uit. Indien niet ingesteld, zijn deze functies uitgeschakeld. Moet overeenkomen met een van de DNS-namen in het certificaat.", "encryption_redirect": "Herleid automatisch naar HTTPS", "encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.", "encryption_https": "HTTPS poort", "encryption_https_desc": "Als de HTTPS-poort is geconfigureerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en biedt deze ook DNS-via-HTTPS op de locatie '/ dns-query'.", "encryption_dot": "DNS-via-TLS poort", "encryption_dot_desc": "Indien deze poort is geconfigureerd, zal AdGuard Home gebruik maken van een DNS-via-TLS server via deze poort.", - "encryption_doq": "DNS-via-QUIC poort (experimenteel)", - "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort. Dit is experimenteel en kan onbetrouwbaar zijn. Er zijn overigens nog niet veel systemen die dit nu al ondersteunen.", + "encryption_doq": "DNS-over-QUIC poort", + "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort.", "encryption_certificates": "Certificaten", "encryption_certificates_desc": "Om encryptie te gebruiken, moet u een geldige SSL certificaat voor uw domein opgeven. U kunt een gratis certificaat krijgen op <0> {{link}} of u kunt het kopen bij een van de vertrouwde certificaatautoriteiten.", "encryption_certificates_input": "Kopieër en plak je PEM-gecodeerde certificaten hier.", @@ -408,7 +410,7 @@ "manual_update": "Volg deze stappen om handmatig bij te werken.", "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt", "clients_title": "Permanente clients", - "clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home", + "clients_desc": "Permanente client-records configureren voor apparaten verbonden met AdGuard Home", "settings_global": "Globaal", "settings_custom": "Aangepast", "table_client": "Gebruiker", @@ -445,7 +447,7 @@ "access_blocked_title": "Niet toegelaten domeinen", "access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".", "access_settings_saved": "Toegangsinstellingen succesvol opgeslagen", - "updates_checked": "Met succes op updates gecontroleerd", + "updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n", "updates_version_equal": "AdGuard Home is actueel", "check_updates_now": "Controleer op updates", "dns_privacy": "DNS Privacy", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 0259d267..7ff4664f 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -104,7 +104,7 @@ "stats_adult": "Blokkerte voksennettsteder", "stats_query_domain": "Mest forespurte domener", "for_last_24_hours": "de siste 24 timene", - "for_last_days": "det siste døgnet", + "for_last_days": "for den siste {{count}} dagen", "for_last_days_plural": "de siste {{count}} dagene", "stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden.", "stats_disabled_short": "Statistikkene har blitt skrudd av", @@ -114,7 +114,7 @@ "top_clients": "Vanligste klienter", "no_clients_found": "Ingen klienter ble funnet", "general_statistics": "Generelle statistikker", - "number_of_dns_query_days": "Antall DNS-forespørsler som ble behandlet det siste døgnet", + "number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene", "number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene", "number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene", "number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister", @@ -199,7 +199,7 @@ "example_upstream_regular": "vanlig DNS (over UDP)", "example_upstream_dot": "kryptert <0>DNS-over-TLS", "example_upstream_doh": "kryptert <0>DNS-over-HTTPS", - "example_upstream_doq": "kryptert <0>DNS-over-QUIC", + "example_upstream_doq": "kryptert <0>DNS-over-QUIC;", "example_upstream_sdns": "du kan bruke <0>DNS-stempler med <1>DNSCrypt eller <2>DNS-over-HTTPS-behandlere", "example_upstream_tcp": "vanlig DNS (over TCP)", "all_lists_up_to_date_toast": "Alle listene er allerede oppdatert", @@ -347,15 +347,15 @@ "encryption_config_saved": "Krypteringsoppsettet ble lagret", "encryption_server": "Tjenerens navn", "encryption_server_enter": "Skriv inn domenenavnet ditt", - "encryption_server_desc": "For å kunne bruke HTTPS, må du skrive inn tjenernavnet som samsvarer med ditt SSL-sertifikat eller jokertegnsertifikat. Hvis feltet er tomt, vil den akseptere TLS-tilkoblinger til ethvert domene.", + "encryption_server_desc": "Hvis angitt, oppdager AdGuard Home klient-IDer, svarer på DDR-spørringer og utfører ytterligere tilkoblingsvalideringer. Hvis ikke angitt, er disse funksjonene deaktivert. Må samsvare med ett av DNS-navnene i sertifikatet.", "encryption_redirect": "Automatisk omdiriger til HTTPS", "encryption_redirect_desc": "Dersom dette er valgt, vil AdGuard Home automatisk omdirigere deg fra HTTP til HTTPS-adresser.", "encryption_https": "HTTPS-port", "encryption_https_desc": "Dersom HTTPS-porten er satt opp, vil AdGuard Home sitt admin-grensesnitt være tilgjengelig gjennom HTTPS, og vil også sørge for DNS-over-HTTPS på «/dns-query»-plasseringen.", "encryption_dot": "'DNS-over-TLS'-port", "encryption_dot_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en 'DNS-over-TLS'-tjener på denne porten.", - "encryption_doq": "'DNS-over-QUIC'-port", - "encryption_doq_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en DNS-over-QUIC-tjener på denne porten. Den er eksperimentell og vil kanskje ikke være pålitelig. I tillegg er det ikke så altfor mange klienter som støtter det for øyeblikket.", + "encryption_doq": "DNS-over-QUIC-port", + "encryption_doq_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en DNS-over-QUIC-tjener på denne porten. ", "encryption_certificates": "Sertifikater", "encryption_certificates_desc": "For å bruke kryptering, må du skrive inn et gyldig SSL-sertifikatkjede for domenet ditt. Du kan få et gratis sertifikat hos <0>{{link}}, eller kjøpe et fra en av de troverdige sertifikatsautoritetene.", "encryption_certificates_input": "Kopier / lim inn dine PEM-kodede sertifikater her.", @@ -429,7 +429,7 @@ "access_blocked_title": "Blokkerte domener", "access_blocked_desc": "Ikke forveksle dette med filtre. AdGuard Home vil nekte å behandle DNS-forespørsler som har disse domenene, og disse forespørslene dukker ikke engang opp i forespørselsloggen. Du kan spesifisere nøyaktige domene navn, jokertegn, eller URL-filterregler, f.eks. «example.org», «*.example.log» eller «||example.org^» derav.", "access_settings_saved": "Tilgangsinnstillingene ble vellykket lagret", - "updates_checked": "Oppdateringene ble vellykket sett etter", + "updates_checked": "En ny versjon av AdGuard Home er tilgjengelig", "updates_version_equal": "AdGuard Home er fullt oppdatert", "check_updates_now": "Se etter oppdateringer nå", "dns_privacy": "DNS-privatliv", diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index 1d1f5f18..65be442a 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nieprawidłowa nazwa serwera", "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"", "form_error_positive": "Musi być większa niż 0", + "form_error_gateway_ip": "Lease nie może mieć adresu IP bramy", "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Musi być niższy niż początek zakresu", "greater_range_start_error": "Musi być większy niż początek zakresu", @@ -117,7 +118,7 @@ "stats_adult": "Zablokowane witryny dla dorosłych", "stats_query_domain": "Najczęściej wyszukiwane domeny", "for_last_24_hours": "przez ostatnie 24 godziny", - "for_last_days": "z ostatniego dnia", + "for_last_days": "za ostatni dzień {{count}}", "for_last_days_plural": "z ostatnich {{count}} dni", "stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień.", "stats_disabled_short": "Statystyki zostały wyłączone", @@ -213,7 +214,7 @@ "example_upstream_udp": "zwykły DNS (przez UDP, nazwa hosta);", "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS;", "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS;", - "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC (eksperymentalny);", + "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>Stempel DNS dla resolwerów <1>DNSCrypt lub <2>DNS-over-HTTPS;", "example_upstream_tcp": "zwykły DNS (przez TCP);", "example_upstream_tcp_hostname": "zwykły DNS (przez TCP, nazwa hosta);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane", "dns_test_ok_toast": "Określone serwery DNS działają poprawnie", "dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie", + "dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo", "unblock": "Odblokuj", "block": "Zablokuj", "disallow_this_client": "Odrzuć tego klienta", @@ -362,15 +364,15 @@ "encryption_config_saved": "Konfiguracja szyfrowania została zapisana", "encryption_server": "Nazwa serwera", "encryption_server_enter": "Wpisz swoją nazwę domeny", - "encryption_server_desc": "Aby korzystać z protokołu HTTPS, musisz wprowadzić nazwę serwera, która jest zgodna z certyfikatem SSL lub certyfikatem typu wildcard. Jeśli pole nie jest ustawione, będzie akceptować połączenia TLS dla dowolnej domeny.", + "encryption_server_desc": "Jeśli jest ustawiony, AdGuard Home wykrywa ClientID, odpowiada na zapytania DDR i wykonuje dodatkowe walidacje połączeń. Jeśli nie jest ustawiony, funkcje te są wyłączone. Musi odpowiadać jednej z nazw DNS w certyfikacie.", "encryption_redirect": "Przekieruj automatycznie do HTTPS", "encryption_redirect_desc": "Jeśli zaznaczone, AdGuard Home automatycznie przekieruje Cię z adresów HTTP na HTTPS.", "encryption_https": "Port HTTPS", "encryption_https_desc": "Jeśli port HTTPS jest skonfigurowany, interfejs administratora AdGuard Home będzie dostępny za pośrednictwem protokołu HTTPS i zapewni DNS przez HTTPS w lokalizacji zapytania '/dns-query'.", "encryption_dot": "Port DNS-over-TLS", "encryption_dot_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-TLS na tym porcie.", - "encryption_doq": "Port DNS-over-QUIC (eksperymentalny)", - "encryption_doq_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-QUIC na tym porcie. Jest to funkcja eksperymentalna i może nie być stabilna. Ponadto, w tej chwili nie ma zbyt wielu klientów, którzy go obsługują.", + "encryption_doq": "Port DNS-over-QUIC", + "encryption_doq_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-QUIC na tym porcie.", "encryption_certificates": "Certyfikaty", "encryption_certificates_desc": "Aby korzystać z szyfrowania, musisz podać prawidłowy łańcuch certyfikatów SSL dla swojej domeny. Możesz uzyskać bezpłatny certyfikat na <0>{{link}} lub możesz go kupić od jednego z zaufanych urzędów certyfikacji.", "encryption_certificates_input": "Kopiuj/wklej tutaj swoje zakodowane certyfikaty PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Niedozwolone domeny", "access_blocked_desc": "Nie należy ich mylić z filtrami. AdGuard Home usuwa zapytania DNS pasujące do tych domen, a zapytania te nie pojawiają się nawet w dzienniku zapytań. Możesz określić dokładne nazwy domen, symbole wieloznaczne lub reguły filtrowania adresów URL, np. \"example.org\", \"*.example.org\" lub \"||example.org^\".", "access_settings_saved": "Ustawienia dostępu zostały pomyślnie zapisane", - "updates_checked": "Aktualizacje pomyślnie sprawdzone", + "updates_checked": "Dostępna jest nowa wersja programu AdGuard Home\n", "updates_version_equal": "AdGuard Home jest aktualny", "check_updates_now": "Sprawdź aktualizacje teraz", "dns_privacy": "Prywatny DNS", diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 48d82e0d..dce24636 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Servidores DNS de inicialização", "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams.", "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 os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não for definido, o AdGuard Home usa os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do AdGuard Home.", + "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_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 de servidor por linha", @@ -47,6 +47,7 @@ "form_error_server_name": "Nome de servidor inválido", "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"", "form_error_positive": "Deve ser maior que 0", + "form_error_gateway_ip": "A concessão não pode ter o endereço IP do gateway", "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Deve ser inferior ao início do intervalo", "greater_range_start_error": "Deve ser maior que o início do intervalo", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);", "example_upstream_dot": "<0>DNS-sobre-TLS criptografado;", "example_upstream_doh": "<0>DNS-sobre-HTTPS criptografado;", - "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado (experimental);", + "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado;", "example_upstream_sdns": "<0>DNS Stamps para o <1>DNSCrypt ou usar os resolvedores <2>DNS-sobre-HTTPS;", "example_upstream_tcp": "DNS regular (através do TCP);", "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso", "dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente", "dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente", + "dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos Solicitações de teste e pode não funcionar corretamente", "unblock": "Desbloquear", "block": "Bloquear", "disallow_this_client": "Não permitir este cliente", @@ -362,15 +364,15 @@ "encryption_config_saved": "Configuração de criptografia salva", "encryption_server": "Nome do servidor", "encryption_server_enter": "Digite seu nome de domínio", - "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.", + "encryption_server_desc": "Se definido, AdGuard Home detecta ClientIDs, responde a consultas DDR, e executa validações de ligações adicionais. Se não estiver definido, estas características são desactivadas. Devem corresponder a um dos Nomes DNS no certificado.", "encryption_redirect": "Redirecionar automaticamente para HTTPS", "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.", "encryption_https": "Porta HTTPS", "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.", "encryption_dot": "Porta DNS-sobre-TLS", "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.", - "encryption_doq": "Porta DNS-sobre-QUIC (experimental)", - "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há muitos clientes que ofereçam suporte no momento.", + "encryption_doq": "Porta DNS-sobre-QUIC", + "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. ", "encryption_certificates": "Certificados", "encryption_certificates_desc": "Para usar criptografia, você precisa fornecer uma cadeia de certificados SSL válida para seu domínio. Você pode obter um certificado gratuito em <0> {{link}} ou pode comprá-lo de uma das autoridades de certificação confiáveis.", "encryption_certificates_input": "Copie/cole aqui seu certificado codificado em PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domínios bloqueados", "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.", "access_settings_saved": "Configurações de acesso foram salvas com sucesso", - "updates_checked": "Atualizações verificadas com sucesso", + "updates_checked": "Uma nova versão do AdGuard Home está disponível\n", "updates_version_equal": "O AdGuard Home está atualizado.", "check_updates_now": "Verificar atualizações", "dns_privacy": "Privacidade de DNS", diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index 1a2d5c7c..6003952e 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que especifica como upstreams.", "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 os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não for definido, o AdGuard Home usa os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do AdGuard Home.", + "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_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 de servidor por linha", @@ -47,6 +47,7 @@ "form_error_server_name": "Nome de servidor inválido", "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"", "form_error_positive": "Deve ser maior que 0", + "form_error_gateway_ip": "A concessão não pode ter o endereço IP do gateway", "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Deve ser inferior ao início do intervalo", "greater_range_start_error": "Deve ser maior que o início do intervalo", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);", "example_upstream_dot": "<0>DNS-sobre-TLS criptografado;", "example_upstream_doh": "<0>DNS-sobre-HTTPS criptografado;", - "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado (experimental);", + "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado;", "example_upstream_sdns": "<0>DNS Stamps para o <1>DNSCrypt ou usar os resolvedores <2>DNS-sobre-HTTPS;", "example_upstream_tcp": "DNS regular (através do TCP);", "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso", "dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente", "dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente", + "dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos solicitações de teste e pode não funcionar corretamente", "unblock": "Desbloquear", "block": "Bloquear", "disallow_this_client": "Não permitir este cliente", @@ -362,15 +364,15 @@ "encryption_config_saved": "Definição de criptografia guardada", "encryption_server": "Nome do servidor", "encryption_server_enter": "Insira o seu nome de domínio", - "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.", + "encryption_server_desc": "Se definido, AdGuard Home detecta ClientIDs, responde a consultas DDR, e executa validações de ligações adicionais. Se não estiver definido, estas características são desactivadas. Devem corresponder a um dos Nomes DNS no certificado.", "encryption_redirect": "Redirecionar automaticamente para HTTPS", "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.", "encryption_https": "Porta HTTPS", "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.", "encryption_dot": "Porta DNS-sobre-TLS", "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.", - "encryption_doq": "Porta DNS-sobre-QUIC (experimental)", - "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há demasiados clientes que ofereçam suporte no momento.", + "encryption_doq": "Porta DNS-sobre-QUIC", + "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. ", "encryption_certificates": "Certificados", "encryption_certificates_desc": "Para usar criptografia, precisa de fornecer uma cadeia de certificados SSL válida para o seu domínio. Pode obter um certificado gratuito em <0> {{link}} ou pode comprá-lo numa das autoridades de certificação confiáveis.", "encryption_certificates_input": "Copie/cole aqui o seu certificado codificado em PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domínios bloqueados", "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.", "access_settings_saved": "Definições de acesso foram guardadas com sucesso", - "updates_checked": "Atualizações verificadas com sucesso", + "updates_checked": "Uma nova versão do AdGuard Home está disponível\n", "updates_version_equal": "O AdGuard Home está atualizado", "check_updates_now": "Verificar atualizações", "dns_privacy": "Privacidade de DNS", diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 097dba59..e0bf4fd0 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nume de server nevalid", "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”", "form_error_positive": "Trebuie să fie mai mare de 0", + "form_error_gateway_ip": "Locația nu poate avea adresa IP a gateway-ului", "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”", "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului", "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului", @@ -213,7 +214,7 @@ "example_upstream_udp": "DNS obișnuit (over UDP, nume de gazdă);", "example_upstream_dot": "<0>DNS-over-TLS criptat;", "example_upstream_doh": "<0>DNS-over-HTTPS criptat;", - "example_upstream_doq": "<0>DNS-over-QUIC criptat (experimental);", + "example_upstream_doq": "criptat <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS Stamps pentru <1>DNSCrypt sau rezolvatori <2>DNS-over-HTTPS;", "example_upstream_tcp": "DNS clasic (over TCP);", "example_upstream_tcp_hostname": "DNS obișnuit (over TCP, nume de gazdă);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes", "dns_test_ok_toast": "Serverele DNS specificate funcționează corect", "dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect", + "dns_test_warning_toast": "„{{key}}” în amonte nu răspunde la solicitările de testare și s-ar putea să nu funcționeze corect", "unblock": "Deblocați", "block": "Blocați", "disallow_this_client": "Nu permiteți acest client", @@ -362,15 +364,15 @@ "encryption_config_saved": "Configurația de criptare salvată", "encryption_server": "Nume de server", "encryption_server_enter": "Introduceți numele domeniului", - "encryption_server_desc": "Pentru a utiliza HTTPS, trebuie să introduceți numele serverului care se potrivește cu certificatul SSL sau certificatul wildcard al dvs. În cazul în care câmpul nu este setat, va accepta conexiuni TLS pentru orice domeniu.", + "encryption_server_desc": "Dacă este setat, AdGuard Home detectează ID-urile de client, răspunde la interogările DDR și efectuează validări suplimentare ale conexiunii. Dacă nu este setat, aceste caracteristici sunt dezactivate. Trebuie să corespundă cu unul dintre numele DNS din certificat.", "encryption_redirect": "Redirecționați automat la HTTPS", "encryption_redirect_desc": "Dacă este bifat, AdGuard Home vă va redirecționa automat de la adrese HTTP la HTTPS.", "encryption_https": "Port HTTPS", "encryption_https_desc": "Dacă portul HTTPS este configurat, interfața administrator AdGuard Home va fi accesibilă prin HTTPS și va oferi de asemenea DNS-over-HTTPS în locația '/DNS-query'.", "encryption_dot": "Port DNS-over-TLS", "encryption_dot_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-TLS pe acest port.", - "encryption_doq": "Port DNS-over-QUIC (experimental)", - "encryption_doq_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-QUIC pe acest port. Este experimental și este posibil să nu fie fiabil. De asemenea, nu există prea mulți clienți care să-l susțină în acest moment.", + "encryption_doq": "Portul DNS-over-QUIC", + "encryption_doq_desc": "Dacă este configurat acest port, AdGuard Home va rula un server DNS-over-QUIC pe acest port.", "encryption_certificates": "Certificate", "encryption_certificates_desc": "Pentru a utiliza criptarea, trebuie furnizate o serie de certificate SSL valabile pentru domeniul dvs.. Puteți obține un certificat gratuit pe <0>{{link}} sau îl puteți cumpăra de la una din Autoritățile Certificate de încredere.", "encryption_certificates_input": "Copiați/lipiți certificatele dvs. PEM-codate aici.", @@ -445,7 +447,7 @@ "access_blocked_title": "Domenii blocate", "access_blocked_desc": "A nu se confunda cu filtrele. AdGuard Home respinge cererile DNS pentru aceste domenii, iar aceste cereri nici măcar nu apar în jurnalul de solicitări. Puteți specifica nume exacte de domenii, metacaractere sau reguli de filtrare URL, cum ar fi \"example.org\", \"*.exemple.org\" sau \"||example.org^\" în mod corespunzător.", "access_settings_saved": "Setările de acces au fost salvate cu succes", - "updates_checked": "Actualizările au fost verificate cu succes", + "updates_checked": "Este disponibilă o nouă versiune de AdGuard Home\n", "updates_version_equal": "AdGuard Home este la zi", "check_updates_now": "Verificați actualizările acum", "dns_privacy": "Confidențialitate DNS", diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index d439d0ff..6a1b6d66 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -47,6 +47,7 @@ "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": "Должно быть больше начала диапазона", @@ -66,7 +67,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-адрес. AdGuard Home не смог определить, использует ли этот сетевой интерфейс статический IP-адрес. Пожалуйста, установите его вручную.", "dhcp_dynamic_ip_found": "Ваша система использует динамический IP-адрес для интерфейса <0>{{interfaceName}}. Чтобы использовать DHCP-сервер, необходимо установить статический IP-адрес. Ваш текущий IP-адрес – <0>{{ipAddress}}. Мы автоматически установим его как статический, если вы нажмёте кнопку «Включить DHCP-сервер».", @@ -163,8 +164,8 @@ "apply_btn": "Применить", "disabled_filtering_toast": "Фильтрация выкл.", "enabled_filtering_toast": "Фильтрация вкл.", - "disabled_safe_browsing_toast": "Антифишинг отключен", - "enabled_safe_browsing_toast": "Антифишинг включен", + "disabled_safe_browsing_toast": "Антифишинг отключён", + "enabled_safe_browsing_toast": "Антифишинг включён", "disabled_parental_toast": "Родительский контроль выкл.", "enabled_parental_toast": "Родительский контроль вкл.", "disabled_safe_search_toast": "Безопасный поиск выкл.", @@ -179,7 +180,7 @@ "edit_table_action": "Редактировать", "delete_table_action": "Удалить", "elapsed": "Затрачено", - "filters_and_hosts_hint": "AdGuard Home распознает базовые правила блокировки и синтаксис файлов hosts.", + "filters_and_hosts_hint": "AdGuard Home распознаёт базовые правила блокировки и синтаксис файлов hosts.", "no_blocklist_added": "Чёрные списки не добавлены", "no_whitelist_added": "Белые списки не добавлены", "add_blocklist": "Добавить чёрный список", @@ -213,7 +214,7 @@ "example_upstream_udp": "обычный DNS (поверх UDP, с именем хоста);", "example_upstream_dot": "зашифрованный <0>DNS-over-TLS;", "example_upstream_doh": "зашифрованный <0>DNS-over-HTTPS;", - "example_upstream_doq": "зашифрованный <0>DNS-over-QUIC (эксперементальный);", + "example_upstream_doq": "зашифрован <0>DNS-over-QUIC", "example_upstream_sdns": "<0>DNS Stamps для <1>DNSCrypt или <2>DNS-over-HTTPS серверов;", "example_upstream_tcp": "обычный DNS (поверх TCP);", "example_upstream_tcp_hostname": "обычный DNS (поверх TCP, с именем хоста);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "DNS-серверы успешно обновлены", "dns_test_ok_toast": "Указанные серверы DNS работают корректно", "dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания", + "dns_test_warning_toast": "Upstream «{{key}}» не отвечает на тестовые запросы и может работать некорректно", "unblock": "Разблокировать", "block": "Заблокировать", "disallow_this_client": "Запретить доступ клиенту", @@ -328,10 +330,10 @@ "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": "Откройте настройки вашего роутера. Обычно вы можете открыть их в вашем браузере, например, http://192.168.0.1/ или http://192.168.1.1/. Вас могут попросить ввести пароль. Если вы не помните его, пароль часто можно сбросить, нажав на кнопку на самом роутере, но помните, что эта процедура может привести к потере всей конфигурации роутера. Если вашему роутеру необходимо приложение для настройки, установите его на свой телефон или ПК и воспользуйтесь им для настройки роутера.", - "install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.", + "install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделённых на 4 группы от одной до трёх цифр.", "install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.", "install_devices_router_list_4": "Вы не можете установить собственный DNS-сервер на некоторых типах маршрутизаторов. В этом случае может помочь настройка AdGuard Home в качестве <0>DHCP-сервера. В противном случае вам следует обратиться к руководству по настройке DNS-серверов для вашей конкретной модели маршрутизатора.", "install_devices_windows_list_1": "Откройте Панель управления через меню «Пуск» или через поиск Windows.", @@ -362,15 +364,15 @@ "encryption_config_saved": "Настройки шифрования сохранены", "encryption_server": "Имя сервера", "encryption_server_enter": "Введите ваше доменное имя", - "encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату или сертификату с поддержкой поддоменов. Если это поле не задано, сервер будет принимать TLS-соединения для любого домена.", + "encryption_server_desc": "Если задано, AdGuard Home распознаёт ClientID, отвечает на DDR-запросы, и дополнительно проверяет соединения. Если не задано, этот функционал отключён. Должно соответствовать одному из параметров DNS Names в сертификате.", "encryption_redirect": "Автоматически перенаправлять на HTTPS", "encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.", "encryption_https": "Порт HTTPS", "encryption_https_desc": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.", "encryption_dot": "Порт DNS-over-TLS", "encryption_dot_desc": "Если этот порт настроен, AdGuard Home запустит DNS-over-TLS-сервер на этому порту.", - "encryption_doq": "Порт DNS-over-QUIC (экспериментальный)", - "encryption_doq_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте. Это экспериментально и может быть ненадёжно. Кроме того, не так много клиентов поддерживает этот способ в настоящий момент.", + "encryption_doq": "Порт DNS-over-QUIC", + "encryption_doq_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте.", "encryption_certificates": "Сертификаты", "encryption_certificates_desc": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на <0>{{link}} или вы можете купить его у одного из доверенных Центров Сертификации.", "encryption_certificates_input": "Скопируйте сюда сертификаты в PEM-кодировке.", @@ -445,7 +447,7 @@ "access_blocked_title": "Неразрешённые домены", "access_blocked_desc": "Не путать с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами. Здесь вы можете уточнить точные имена доменов, шаблоны, правила URL-фильтрации, например, «example.org», «*.example.org» или «||example.org».", "access_settings_saved": "Настройки доступа успешно сохранены", - "updates_checked": "Проверка обновлений прошла успешно", + "updates_checked": "Доступна новая версия AdGuard Home", "updates_version_equal": "Версия AdGuard Home актуальна", "check_updates_now": "Проверить обновления", "dns_privacy": "Зашифрованный DNS", @@ -589,7 +591,7 @@ "validated_with_dnssec": "Подтверждено с помощью DNSSEC", "all_queries": "Все запросы", "show_blocked_responses": "Заблокировано", - "show_whitelisted_responses": "В белом списке", + "show_whitelisted_responses": "Разрешённые", "show_processed_responses": "Обработан", "blocked_safebrowsing": "Заблокировано согласно базе данных Safe Browsing", "blocked_adult_websites": "Заблокировано Родительским контролем", diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index ea6d29b1..17802771 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -5,10 +5,11 @@ "load_balancing": "ධාරිතාව තුලනය", "local_ptr_title": "පෞද්ගලික ප්‍රතිවර්ත ව.නා.ප. සේවාදායක", "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින සහිත අනුග්‍රාහකවල සත්කාරක නාම විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.", - "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. විසඳුම් භාවිතා කරයි: {{ip}}.", - "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. විසඳුම් නිශ්චය කරගත නොහැකි විය.", + "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.", + "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.", "local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න", "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න", + "use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය", "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක සඳහා පරීක්‍ෂා කරන්න", "save_config": "වින්‍යාසය සුරකින්න", "enabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය සබල කෙරිණි", @@ -16,9 +17,14 @@ "unavailable_dhcp": "ග.ධා.වි.කෙ. නැත", "unavailable_dhcp_desc": "ඇඩ්ගාර්ඩ් හෝම් හට ඔබගේ මෙහෙයුම් පද්ධතියේ ග.ධා.වි.කෙ. සේවාදායකයක් ධාවනය කිරීමට නොහැකිය", "dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)", - "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි ඇති ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.", + "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි තිළෑලි ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.", "dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න", "dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න", + "dhcp_not_found": "ඇඩ්ගාර්ඩ් හෝම් සඳහා ජාලයෙහි කිසිදු ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් හමු නොවූ නිසා තිළෑලි සේවාදායකය සබල කිරීම ආරක්‍ෂිත වේ. කෙසේ වෙතත්, ස්වයංක්‍රීය ඒෂණය ඉතා නිවැරදි නොවිය හැකි බැවින් ඔබ එය අතින් නැවත පරීක්‍ෂා කළ යුතුය.", + "dhcp_found": "ක්‍රියාත්මක ග.ධා.වි.කෙ සේවාදායකයක් ජාලය තුළ හමු විය. තිළෑලි ග.ධා.වි.කෙ සේවාදායකය සබල කිරීම ආරක්‍ෂිත නොවේ.", + "dhcp_leases": "ග.ධා.වි.කෙ. කල්පැවරීම", + "dhcp_static_leases": "ස්ථිර ග.ධා.වි.කෙ. කල්පැවරීම", + "dhcp_leases_not_found": "ග.ධා.වි.කෙ. කල්පැවරීම් නැත", "dhcp_config_saved": "ග.ධා.වි.කෙ. වින්‍යාසය සාර්ථකව සුරකින ලදි", "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 4 සැකසුම්", "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්", @@ -31,6 +37,7 @@ "form_error_mac_format": "මා.ප්‍ර.පා. ලිපිනය වලංගු නොවේ", "form_error_client_id_format": "අනුග්‍රාහකයේ හැඳු. වලංගු නොවේ", "form_error_server_name": "සේවාදායකයේ නම වලංගු නොවේ", + "form_error_subnet": "\"{{cidr}}\" අනුජාලය හි \"{{ip}}\" අ.ජා.කෙ. ලිපිනය අඩංගු නොවේ", "form_error_positive": "0 ට වඩා වැඩි විය යුතුය", "out_of_range_error": "\"{{start}}\"-\"{{end}}\" පරාසයෙන් පිට විය යුතුය", "lower_range_start_error": "පරාසය ආරම්භයට වඩා අඩු විය යුතුය", @@ -40,6 +47,8 @@ "dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය", "dhcp_form_range_start": "පරාසය ආරම්භය", "dhcp_form_range_end": "පරාසය අවසානය", + "dhcp_form_lease_title": "ග.ධා.වි.කෙ. කල්පැවරීම (තත්. වලින්)", + "dhcp_form_lease_input": "කල්පැවරීමේ පරාසය", "dhcp_interface_select": "ග.ධා.වි.කෙ. අතුරුමුහුණත තෝරන්න", "dhcp_hardware_address": "දෘඩාංග ලිපිනය", "dhcp_ip_addresses": "අ.ජා.කෙ. (IP) ලිපින", @@ -50,6 +59,14 @@ "dhcp_error": "ජාලයේ තවත් ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය", "dhcp_static_ip_error": "ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් සැකසිය යුතුය. මෙම ජාල අතුරුමුහුණත ස්ථිතික අ.ජා. කෙ. ලිපිනයක් භාවිතයෙන් වින්‍යාසගත කර තිබේද යන්න තීරණය කිරීමට ඇඩ්ගාර්ඩ් හෝම් අසමත් විය. කරුණාකර ස්ථිතික අ.ජා. කෙ. ලිපිනයක් අතින් සකසන්න.", "dhcp_dynamic_ip_found": "ඔබගේ පද්ධතිය <0>{{interfaceName}} අතුරු මුහුණත සඳහා ගතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපින වින්‍යාසය භාවිතා කරයි. ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අ.ජා. කෙ. ලිපිනයක් සැකසිය යුතුය. ඔබගේ වර්තමාන අ.ජා. කෙ. ලිපිනය <0>{{ipAddress}} වේ. ඔබ \"ග.ධා.වි.කෙ. සබල කරන්න\" බොත්තම එබුවහොත් ඇඩ්ගාර්ඩ් හෝම් ස්වයංක්‍රීයව මෙම අ.ජා. කෙ. ලිපිනය ස්ථිතික ලෙස සකසනු ඇත.", + "dhcp_lease_added": "\"{{key}}\" ස්ථිර කල්පැවරීම එකතු කෙරිණි", + "dhcp_lease_deleted": "\"{{key}}\" ස්ථිර කල්පැවරීම මකා දැමිණි", + "dhcp_new_static_lease": "නව ස්ථිර කල්පැවරීම", + "dhcp_static_leases_not_found": "ග.ධා.වි.කෙ. ස්ථිර කල්පැවරීම් නැත", + "dhcp_add_static_lease": "ස්ථිර කල්පැවරීමක් යොදන්න", + "dhcp_reset_leases": "කල්පැවරීම් යළි සකසන්න", + "dhcp_reset_leases_confirm": "සියළු කල්පැවරීම් යළි සැකසීමට වුවමනා ද?", + "dhcp_reset_leases_success": "ග.ධා.වි.කෙ. කල්පැවරීම් යළි සැකසිණි", "dhcp_reset": "ග.ධා.වි.කෙ. වින්‍යාසය යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", "country": "රට", "city": "නගරය", @@ -112,6 +129,7 @@ "block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න", "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති පෙරහන් තුළ පිහිටුවිය හැකිය.", "use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව භාවිතා කරන්න", + "use_adguard_browsing_sec_hint": "ඇඩ්ගාර්ඩ් හෝම් විසින් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මගින් වසම අවහිර කර ඇත්දැයි පරීක්‍ෂා කරයි. එය සිදු කිරීමට රහස්‍යතා-හිතකාමී බැලීමේ යෙ.ක්‍ර.මු. භාවිතා කෙරේ: වසමේ කෙටි උපසර්ගයක SHA256 පූරකයක් පමණක් සේවාදායකය වෙත යවනු ලැබේ.", "use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න", "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", "enforce_safe_search": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න", @@ -179,13 +197,13 @@ "example_comment_hash": "# එසේම අදහස් දැක්වීමක්.", "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.", "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);", - "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක නම හරහා);", - "example_upstream_dot": "සංකේතිත <0>DNS-over-TLS", - "example_upstream_doh": "සංකේතිත <0>DNS-over-HTTPS", - "example_upstream_doq": "සංකේතිත <0>DNS-over-QUIC;", - "example_upstream_sdns": "<1>DNSCrypt හෝ <2>HTTPS-හරහා-ව.නා.ප. විසඳුම් සඳහා <0>ව.නා.ප. මුද්දර;", + "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);", + "example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.;", + "example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.;", + "example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.;", + "example_upstream_sdns": "<1>DNSCrypt හෝ <2>HTTPS-මගින්-ව.නා.ප. පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර;", "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);", - "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක නම හරහා);", + "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);", "all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි", "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි", "dns_test_not_ok_toast": "\"{{key}}\" සේවාදායක(ය): භාවිතා කිරීමට නොහැකි විය, ඔබ එය නිවැරදිව ලියා ඇතිදැයි පරීක්‍ෂා කරන්න", @@ -242,14 +260,14 @@ "blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම", "blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම", "dnscrypt": "DNSCrypt", - "dns_over_https": "HTTPS-හරහා-ව.නා.ප.", - "dns_over_tls": "TLS-හරහා-ව.නා.ප.", - "dns_over_quic": "QUIC-හරහා-ව.නා.ප.", + "dns_over_https": "HTTPS-මගින්-ව.නා.ප.", + "dns_over_tls": "TLS-මගින්-ව.නා.ප.", + "dns_over_quic": "QUIC-මගින්-ව.නා.ප.", "client_id": "අනුග්‍රාහකයේ හැඳු.", "client_id_placeholder": "අනුග්‍රාහකයක හැඳු. යොදන්න", "client_id_desc": "අනුග්‍රාහක හැඳු. මගින් අනුග්‍රාහක හඳුනාගත හැකිය. කෙසේදැයි මෙතැනින් දැන ගන්න.", - "download_mobileconfig_doh": "HTTPS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න", - "download_mobileconfig_dot": "TLS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න", + "download_mobileconfig_doh": "HTTPS-මගින්-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න", + "download_mobileconfig_dot": "TLS-මගින්-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න", "download_mobileconfig": "වින්‍යාසගත ගොනුව බාගන්න", "plain_dns": "සරල ව.නා.ප.", "form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න", @@ -295,12 +313,13 @@ "install_submit_title": "සුභ පැතුම්!", "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ දැන් ඇඩ්ගාර්ඩ් හෝම් භාවිතය ආරම්භ කිරීමට සූදානම්ය.", "install_devices_router": "මාර්ගකාරකය", - "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.", + "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධිත සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.", "install_devices_address": "ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය පහත ලිපිනයන්ට සවන් දෙමින් පවතී", "install_devices_router_list_1": "ඔබගේ මාර්ගකාරකය සඳහා වූ මනාපයන් විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) ප්‍රවේශ විය හැකිය. මුරපදය ඇතුල් කිරීමට සිදු විය හැකි නමුත් එය මතක නැතිනම් බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසිය හැකිය. නමුත් මෙම ක්‍රියා පටිපාටිය තෝරා ගන්නේ නම්, බොහෝ විට ඔබගේ මාර්ගකාරකයේ සමස්ථ වින්‍යාසය අහිමි වනු ඇති බව මතක තබා ගන්න.එය පිහිටුවීමට ඔබගේ මාර්ගකාරකයට යෙදුමක් ඇවැසි නම්, කරුණාකර එය ඔබගේ පරිගණකයේ හෝ දුරකථනයේ ස්ථාපනය කර මාර්ගකාරකයේ සැකසුම් වෙත ප්‍රවේශ වීමට භාවිතා කරන්න.", "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. අංක කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්ෂේත්‍රයක් අසල ඇති ව.නා.ප. අකුරු බලන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.", "install_devices_router_list_3": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින එහි ඇතුල් කරන්න.", "install_devices_router_list_4": "සමහර වර්ගයේ මාර්ගකාරක වල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසීමට නොහැකිය. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක් ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරකය සඳහා වූ ව.නා.ප. සේවාදායක රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත පරීක්‍ෂා කළ යුතුය.", + "install_devices_windows_list_1": "පාලන වට්ටෝරුව හෝ වින්ඩෝස් සෙවුම හරහා පාලන මඬල අරින්න.", "install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.", "install_devices_windows_list_3": "වම් තීරුවෙහි \"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" ඔබන්න.", "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.", @@ -310,8 +329,8 @@ "install_devices_macos_list_2": "ජාලය මත ඔබන්න.", "install_devices_macos_list_3": "ඔබගේ ලැයිස්තුවේ පළමු සම්බන්ධතාවය තෝරා වැඩිදුර යන්න ඔබන්න.", "install_devices_macos_list_4": "ව.නා.ප. (DNS) තීරුව තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", - "install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මෙනුවෙහි මුල් තිරයෙන්, සැකසීම් මත තට්ටු කරන්න.", - "install_devices_android_list_2": "මෙනුවේ වයි-ෆයි මත තට්ටු කරන්න. පවතින සියලුම ජාල ලැයිස්තුගත කර ඇති තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි ව.නා.ප. සැකසිය නොහැක).", + "install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මුල් තිරයෙන්, සැකසුම් මත තට්ටු කරන්න.", + "install_devices_android_list_2": "වට්ටෝරුවෙහි වයි-ෆයි මත තට්ටු කරන්න. පවතින සියළුම ජාල ලේඛන ගතවී තිබෙන තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි ව.නා.ප. සැකසීමට නොහැකිය).", "install_devices_android_list_3": "සම්බන්ධිත ජාලය මත දිගු වේලාවක් ඔබන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.", "install_devices_android_list_4": "ඔබට සමහර උපාංගවල සියළු සැකසුම් බැලීමට \"වැඩිදුර\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්‍ය විය හැකිය. එමෙන්ම ඔබගේ ඇන්ඩ්‍රොයිඩ් ව.නා.ප. (DNS) සැකසුම් වෙනස් කිරීමට අ.ජා.කෙ. (IP) සැකසුම්, ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත මාරු කළ යුතුය.", "install_devices_android_list_5": "ව.නා.ප. 1 සහ ව.නා.ප. 2 පිහිටුවීම් අගයන් ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින වලට වෙනස් කරන්න.", @@ -328,22 +347,24 @@ "encryption_config_saved": "සංකේතන වින්‍යාසය සුරකින ලදි", "encryption_server": "සේවාදායක‌‌‌‌යේ නම", "encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුල් කරන්න", + "encryption_server_desc": "සැකසා ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් අනුග්‍රාහක හැඳුනුම් හඳුනා ගැනෙයි, සෘ.ද.ඉ. (DDR) විමසුම්වලට ප්‍රතිචාර දක්වයි, සහ අතිරේක සම්බන්ධතා වලංගුකරණය සිදු කරයි. නොඑසේ නම්, මෙම විශේෂාංග අබලව ඇත. සහතිකයේ තිබෙන ව.නා.ප. නම් වලින් එකකට ගැළපිය යුතුය.", "encryption_redirect": "ස්වයංක්‍රීයව HTTPS වෙත හරවා යවන්න", "encryption_redirect_desc": "සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් ඔබව ස්වයංක්‍රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.", "encryption_https": "HTTPS තොට", - "encryption_https_desc": "HTTPS තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-හරහා-ව.නා.ප. ද ලබා දෙනු ඇත.", - "encryption_dot": "TLS-හරහා-ව.නා.ප. තොට", - "encryption_dot_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.", - "encryption_doq": "QUIC-හරහා-ව.නා.ප. තොට", - "encryption_doq_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත. එය පරීක්‍ෂාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්‍රාහක නැත.", + "encryption_https_desc": "HTTPS තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-මගින්-ව.නා.ප. ද ලබා දෙනු ඇත.", + "encryption_dot": "TLS-මගින්-ව.නා.ප. තොට", + "encryption_dot_desc": "මෙම තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-මගින්-ව.නා.ප. සේවාදායකයක් ධාවනය කෙරේ.", + "encryption_doq": "QUIC-මගින්-ව.නා.ප. තොට", + "encryption_doq_desc": "මෙම තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-මගින්-ව.නා.ප. සේවාදායකයක් ධාවනය කෙරේ.", "encryption_certificates": "සහතික", + "encryption_certificates_desc": "සංකේතනය භාවිතයට, ඔබගේ වසම සඳහා වලංගු SSL සහතික දාමයක් සැපයිය යුතුය. <0>{{link}} වෙතින් නොමිලේ සහතිකයක් ලබා ගැනීමට හැකිය හෝ විශ්වාසදායක සහතික අධිකාරියකින් මිලදී ගන්න.", "encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.", "encryption_status": "තත්වය", "encryption_expire": "කල් ඉකුත් වීම", "encryption_key": "පුද්ගලික යතුර", "encryption_key_input": "ඔබගේ සහතිකය සඳහා PEM-කේතනය කළ පුද්ගලික යතුර පිටපත් කර මෙහි අලවන්න.", - "encryption_enable": "සංකේතනය සබල කරන්න (HTTPS, DNS-over-HTTPS සහ DNS-over-TLS)", - "encryption_enable_desc": "සංකේතනය සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර ව.නා.ප. සේවාදායකය DNS-over-HTTPS සහ DNS-over-TLS හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.", + "encryption_enable": "සංකේතනය සබල කරන්න (HTTPS, HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප.)", + "encryption_enable_desc": "සංකේතනය සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර ව.නා.ප. සේවාදායකය HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප. හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.", "encryption_chain_valid": "සහතික දාමය වලංගු ය", "encryption_chain_invalid": "සහතික දාමය වලංගු නොවේ", "encryption_key_valid": "මෙය වලංගු {{type}} පුද්ගලික යතුරකි", @@ -383,6 +404,7 @@ "client_edit": "අනුග්‍රාහකය සංස්කරණය", "client_identifier": "හඳුන්වනය", "ip_address": "අ.ජා.කෙ. ලිපිනය", + "client_identifier_desc": "අ.ජා.කෙ. (IP) ලිපින, අන.ජා. (CIDR), මා.ප්‍ර.පා. (MAC) ලිපින හෝ අනුග්‍රාහක හැඳුනුමක් (DoT/DoH/DoQ සඳහා භාවිතා කළ හැකිය) මගින් අනුග්‍රාහක හඳුනාගත හැකිය. අනුග්‍රාහක හඳුනා ගන්නේ කෙසේද යන්න පිළිබඳව <0>මෙතැනින් තව දැනගන්න.", "form_enter_ip": "අ.ජා.කෙ. (IP) ඇතුල් කරන්න", "form_enter_subnet_ip": "\"{{cidr}}\" අනුජාලයෙහි අ.ජා.කෙ. ලිපිනයක් යොදන්න.", "form_enter_mac": "මා.ප්‍ර.පා. (MAC) යොදන්න", @@ -401,25 +423,32 @@ "access_title": "ප්‍රවේශවීමට සැකසුම්", "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකයට ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය", "access_allowed_title": "ඉඩ ලත් අනුග්‍රාහකයින්", - "access_allowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.", + "access_allowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.", "access_disallowed_title": "නොඉඩ ලත් අනුග්‍රාහකයින්", - "access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.", + "access_disallowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ අනුග්‍රාහක හැඳු. ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් ඉල්ලීම් අත්හරිනු ඇත. ඉඩ ලත් අනුග්‍රාහකවල නිවේශිත තිබේ නම්, මෙම ක්‍ෂේත්‍රය නොසලකා හරිනු ඇත.", "access_blocked_title": "නොඉඩ ලත් වසම්", "access_settings_saved": "ප්‍රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි", - "updates_checked": "යාවත්කාල සාර්ථකව පරික්‍ෂා කෙරිණි", + "updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ", "updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි", "check_updates_now": "දැන් යාවත්කාල පරීක්‍ෂා කරන්න", "dns_privacy": "ව.නා.ප. රහස්‍යතා", + "setup_dns_privacy_1": "<0>TLS-මගින්-ව.නා.ප. සඳහා <1>{{address}}.", + "setup_dns_privacy_2": "<0>HTTPS-මගින්-ව.නා.ප. සඳහා <1>{{address}}.", "setup_dns_privacy_3": "<0>මෙහි ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් ඇත.", - "setup_dns_privacy_android_2": "<1>HTTPS-හරහා-ව.නා.ප. සහ <1>TLS-හරහා-ව.නා.ප. සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ් සහාය දක්වයි.", + "setup_dns_privacy_android_1": "TLS-මගින්-ව.නා.ප සහාය සමගම ඇන්ඩ්‍රොයිඩ් 9 පැමිණේ. එය වින්‍යාස කිරීමට, සැකසුම් → ජාලය හා අන්තර්ජාලය → වැඩිදුර → පෞද්. ව.නා.ප. වෙත ගොස් එහි ඔබගේ වසමේ නම යොදන්න.", + "setup_dns_privacy_android_2": "<1>HTTPS-මගින්-ව.නා.ප. හා <1>TLS-මගින්-ව.නා.ප. සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ් සහාය දක්වයි.", + "setup_dns_privacy_android_3": "<0>ඉන්ට්‍රා විසින් <1>HTTPS-මගින්-ව.නා.ප සහාය ඇන්ඩ්‍රොයිඩ් සඳහා එකතු කරයි.", + "setup_dns_privacy_ios_2": "<1>HTTPS-මගින්-ව.නා.ප. හා <1>TLS-මගින්-ව.නා.ප. සඳහා <0>අයිඕඑස් සඳහා ඇඩ්ගාර්ඩ් සහාය දක්වයි.", "setup_dns_privacy_other_title": "වෙනත් ක්‍රියාවට නැංවූ දෑ", - "setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්‍රොක්සි දන්නා සියලුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.", - "setup_dns_privacy_other_3": "<1>DNS-over-HTTPS සඳහා <0>dnscrypt-පෙරකලාසිය සහාය දක්වයි.", - "setup_dns_privacy_other_4": "<1>DNS-over-HTTPS සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස් සහාය දක්වයි.", + "setup_dns_privacy_other_1": "ඇඩ්ගාර්ඩ් හෝම් මෘදුකාංගයට ඕනෑම වේදිකාවක ආරක්‍ෂිත ව.නා.ප. අනුග්‍රාහකයක් ලෙස ක්‍රියාත්මක වීමට ද හැකිය.", + "setup_dns_privacy_other_2": "<0>ව.නා.ප. ප්‍රතියුක්තය දන්නා සියළුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.", + "setup_dns_privacy_other_3": "<1>HTTPS-මගින්-ව.නා.ප. සඳහා <0>dnscrypt-ප්‍රතියුක්තය සහාය දක්වයි.", + "setup_dns_privacy_other_4": "<1>HTTPS-මගින්-ව.නා.ප. සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස් සහාය දක්වයි.", "setup_dns_privacy_other_5": "<0>මෙහි සහ <1>මෙහි තවත් ක්‍රියාවට නැංවූ දෑ ඔබට හමුවනු ඇත.", "setup_dns_privacy_ioc_mac": "අයිඕඑස් සහ මැක්ඕඑස් වින්‍යාසය", - "setup_dns_notice": "ඔබට <1>DNS-over-HTTPS හෝ <1>DNS-over-TLS භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත කිරීමට ඇවැසිය.", + "setup_dns_notice": "ඔබට <1>HTTPS-මගින්-ව.නා.ප. හෝ <1>DNS-මගින්-ව.නා.ප. භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත කළ යුතුය.", "rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කෙරිණි", + "rewrite_deleted": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කෙරිණි", "rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න", "rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවිණි", "rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", @@ -462,7 +491,7 @@ "statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම", "statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත", "statistics_clear": "සංඛ්‍යාලේඛන හිස් කරන්න", - "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", + "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට වුවමනා ද?", "statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත", "statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කෙරිණි", "statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න", @@ -531,6 +560,7 @@ "list_updated": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි", "list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි", "dnssec_enable": "DNSSEC සබල කරන්න", + "validated_with_dnssec": "DNSSEC සමඟ වලංගු කෙරිණි", "all_queries": "සියළුම විමසුම්", "show_blocked_responses": "අවහිර කර ඇත", "show_whitelisted_responses": "ඉඩ දී ඇත", diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index 52048c04..b4fa2955 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -47,6 +47,7 @@ "form_error_server_name": "Neplatné meno servera", "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"", "form_error_positive": "Musí byť väčšie ako 0", + "form_error_gateway_ip": "Prenájom nemôže mať IP adresu brány", "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu", "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu", @@ -213,7 +214,7 @@ "example_upstream_udp": "štandardné DNS (cez UDP, hostname);", "example_upstream_dot": "šifrované <0>DNS-over-TLS;", "example_upstream_doh": "šifrované <0>DNS-over-HTTPS;", - "example_upstream_doq": "šifrované <0>DNS-over-QUIC (experimentálne);", + "example_upstream_doq": "šifrované <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS pečiatky pre <1>DNSCrypt alebo <2>DNS-over-HTTPS rezolvery;", "example_upstream_tcp": "obyčajná DNS (cez TCP);", "example_upstream_tcp_hostname": "štandardné DNS (cez TCP, hostname);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream servery boli úspešne uložené", "dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne", "dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali", + "dns_test_warning_toast": "Upstream \"{{key}}\" neodpovedá na testovacie dopyty a nemusí fungovať správne", "unblock": "Odblokovať", "block": "Blokovať", "disallow_this_client": "Zablokovať tohto klienta", @@ -362,15 +364,15 @@ "encryption_config_saved": "Konfigurácia šifrovania uložená", "encryption_server": "Meno servera", "encryption_server_enter": "Zadajte meno Vašej domény", - "encryption_server_desc": "Ak chcete používať protokol HTTPS, musíte zadať názov servera, ktorý zodpovedá Vášmu certifikátu SSL alebo certifikátu so zástupnými znakmi. Ak pole nie je nastavené, bude akceptovať TLS pripojenia pre ľubovoľnú doménu.", + "encryption_server_desc": "Ak je nastavené, AdGuard Home zisťuje ClientID, odpovedá na dotazy DDR a vykonáva ďalšie overenia pripojenia. Ak nie je nastavená, tieto funkcie sú vypnuté. Musí sa zhodovať s jedným z názvov DNS v certifikáte.", "encryption_redirect": "Automaticky presmerovať na HTTPS", "encryption_redirect_desc": "Ak je táto možnosť začiarknutá, služba AdGuard Home Vás automaticky presmeruje z adresy HTTP na adresy HTTPS.", "encryption_https": "HTTPS port", "encryption_https_desc": "Ak je nakonfigurovaný HTTPS port, AdGuard Home administrátorské rozhranie bude prístupné cez HTTPS a bude tiež poskytovať DNS-cez-HTTPS na '/dns-query'.", "encryption_dot": "Port DNS-cez-TLS", "encryption_dot_desc": "Ak je tento port nakonfigurovaný, AdGuard Home bude na tomto porte spúšťať DNS-cez-TLS server.", - "encryption_doq": "DNS-over-QUIC (experimentálne)", - "encryption_doq_desc": "Ak je tento port nakonfigurovaný, AdGuard Home na tomto porte spustí server DNS-over-QUIC. Je to experimentálne a nemusí to byť spoľahlivé. Momentálne tiež nie je príliš veľa klientov, ktorí by ju podporovali.", + "encryption_doq": "Port DNS-cez-QUIC", + "encryption_doq_desc": "Ak je tento port nakonfigurovaný, AdGuard Home na tomto porte spustí server DNS-over-QUIC. ", "encryption_certificates": "Certifikáty", "encryption_certificates_desc": "Ak chcete používať šifrovanie, musíte pre svoju doménu poskytnúť platný reťazec certifikátov SSL. Certifikát môžete získať bezplatne na adrese <0>{{link}} alebo si ho môžete kúpiť od jedného z dôveryhodných certifikačných orgánov.", "encryption_certificates_input": "Skopírujte alebo prilepte sem certifikáty vo formáte PEM.", @@ -445,7 +447,7 @@ "access_blocked_title": "Nepovolené domény", "access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.", "access_settings_saved": "Nastavenia prístupu úspešne uložené", - "updates_checked": "Aktualizácie úspešne skontrolované", + "updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n", "updates_version_equal": "AdGuard Home je aktuálny", "check_updates_now": "Skontrolovať aktualizácie teraz", "dns_privacy": "DNS súkromie", diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index 8dffefc7..1e89cc66 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Zagonski DNS strežniki", "bootstrap_dns_desc": "Zagonski DNS strežniki se uporabljajo za razreševanje IP naslovov DoH/DoT reševalcev, ki jih določite kot navzgornje.", "local_ptr_title": "Zasebni povratni strežniki DNS", - "local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne poizvedbe PTR. Ti strežniki se uporabljajo za razreševanje imen gostiteljev z zasebnimi naslovi IP, na primer \"192.168.12.34\" uporablja DNS. Če ni nastavljen, uporablja naslove privzetih razreševalnikov DNS vašega OS, razen naslovov samega AdGuard Home.", + "local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne PTR poizvedbe. Ti strežniki se uporabljajo za reševanje zahtev PTR za naslove v zasebnih obsegih IP, na primer \"192.168.12.34\", z uporabo obratnega DNS. Če ni nastavljen, AdGuard Home uporablja naslove privzetih razreševalnikov DNS vašega OS, razen naslovov samega AdGuard Home.", "local_ptr_default_resolver": "AdGuard Home privzeto uporablja te povratne razreševalnike DNS: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home ni mogel določiti ustreznih zasebnih povratnih reševalcev DNS za ta sistem.", "local_ptr_placeholder": "V vrstico vnesite en naslov strežnika", @@ -47,6 +47,7 @@ "form_error_server_name": "Neveljavno ime strežnika", "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"", "form_error_positive": "Mora biti večja od 0", + "form_error_gateway_ip": "Najem ne more imeti naslova IP prehoda", "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Mora biti manjši od začetka razpona", "greater_range_start_error": "Mora biti večji od začetka razpona", @@ -213,7 +214,7 @@ "example_upstream_udp": "redni DNS (nad UDP, ime gostitelja);", "example_upstream_dot": "šifriran <0>DNS-prek-TLS;", "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS;", - "example_upstream_doq": "šifriran <0>DNS-prek-QUIC (eksperimentalno);", + "example_upstream_doq": "šifriran <0>DNS-prek-QUIC;", "example_upstream_sdns": "lahko uporabite <0>DNS Žige za reševalce <1>DNSCrypt ali <2>DNS-prek-HTTPS;", "example_upstream_tcp": "redni DNS (nad TCP);", "example_upstream_tcp_hostname": "redni DNS (nad TCP, ime gostitelja);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni", "dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno", "dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali", + "dns_test_warning_toast": "Upstream \"{{key}}\" se ne odziva na testne zahteve in morda ne deluje pravilno", "unblock": "Omogoči", "block": "Onemogoči", "disallow_this_client": "Onemogoči tega odjemalca", @@ -351,7 +353,7 @@ "install_devices_android_list_5": "Spremeni nastavitev vrednosti DNS 1 in DNS 2 na naslove strežnikov AdGuard Home.", "install_devices_ios_list_1": "Na začetnem zaslonu izberite Nastavitve.", "install_devices_ios_list_2": "V levem meniju izberite Wi-Fi (nemogoče je konfigurirati DNS za mobilna omrežja).", - "install_devices_ios_list_3": "Dotaknite se imena trenutno aktivnega omrežja.", + "install_devices_ios_list_3": "Tapnite na ime trenutno aktivnega omrežja.", "install_devices_ios_list_4": "V polje DNS vnesite vaše naslove AdGuard Home strežnika.", "get_started": "Začnimo", "next": "Naprej", @@ -362,15 +364,15 @@ "encryption_config_saved": "Nastavitve šifriranja so shranjene", "encryption_server": "Ime strežnika", "encryption_server_enter": "Vnesite ime vaše domene", - "encryption_server_desc": "Za uporabo HTTPS morate vnesti ime strežnika, ki se ujema z vašim digitalnim certifikatom SSL.\n", + "encryption_server_desc": "Če je nastavljeno, AdGuard Home zazna ClientID-je, odgovori na poizvedbe DDR in izvede dodatna preverjanja povezave. Če ni nastavljeno, so te funkcije onemogočene. Ujemati se mora z enim od imen DNS v potrdilu.", "encryption_redirect": "Samodejno preusmeri na HTTPS", "encryption_redirect_desc": "Če je označeno, vas bo AdGuard Home samodejno preusmeril iz naslovov HTTP na naslove HTTPS.", "encryption_https": "Vrata HTTPS", "encryption_https_desc": "Če so vrata HTTPS konfigurirana, bo skrbniški vmesnik AdGuard Home dostopen prek protokola HTTPS, prav tako pa bo zagotovil DNS-prek-HTTPS na mestu '/dns-query'.", "encryption_dot": "Vrata DNS-prek-TLS", "encryption_dot_desc": "Če so ta vrata konfigurirana, bo AdGuard Home na teh vratih zagnal DNS-prek-TLS strežnika.", - "encryption_doq": "DNS-prek-vrat QUIC (eksperimentalno)", - "encryption_doq_desc": "Če so nastavljena ta vrata bo AdGuard Home na teh vratih zagnal strežnik DNS-prek-QUIC. To je eksperimentalno in morda ni zanesljivo. Prav tako trenutno ni preveč odjemalcev, ki to podpirajo.", + "encryption_doq": "DNS-prek-vrat QUIC", + "encryption_doq_desc": "Če so nastavljena ta vrata bo AdGuard Home na teh vratih zagnal strežnik DNS-prek-QUIC. ", "encryption_certificates": "Digitalna potrdila", "encryption_certificates_desc": "Za uporabo šifriranja morate za svojo domeno zagotoviti veljavno verigo potrdil SSL. Brezplačno digitalno potrdilo lahko dobite na <0>{{link}} ali pa ga kupite pri enem od zaupanja vrednih overiteljev.\n\n", "encryption_certificates_input": "Tukaj kopirajte/prilepite PEM šifrirana digitalna potrdila.", @@ -445,7 +447,7 @@ "access_blocked_title": "Prepovedane domene", "access_blocked_desc": "Ne gre zamenjati s filtri. AdGuard Home spusti poizvedbe DNS, ki se ujemajo s temi domenami, in te poizvedbe se niti ne pojavijo v dnevniku poizvedb. Določite lahko natančna imena domen, nadomestne znake ali pravila filtriranja URL-jev, npr. ustrezno \"example.org\", \"*.example.org\" ali \"|| example.org ^\".", "access_settings_saved": "Nastavitve dostopa so uspešno shranjene", - "updates_checked": "Posodobitve so uspešno preverjene", + "updates_checked": "Na voljo je nova različica programa AdGuard Home\n", "updates_version_equal": "AdGuard Home je posodobljen", "check_updates_now": "Preveri obstoj posodobitev zdaj", "dns_privacy": "Zasebnost DNS", diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json index 1fff431a..c42b57ad 100644 --- a/client/src/__locales/sr-cs.json +++ b/client/src/__locales/sr-cs.json @@ -47,6 +47,7 @@ "form_error_server_name": "Nevažeće ime servera", "form_error_subnet": "Subnet \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"", "form_error_positive": "Mora biti veće od 0", + "form_error_gateway_ip": "Zakup ne može imati IP adresu mrežnog prolaza", "out_of_range_error": "Mora biti izvan opsega \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Mora biti manje od početnog opsega", "greater_range_start_error": "Mora biti veće od početnog opsega", @@ -213,7 +214,7 @@ "example_upstream_udp": "uobičajen DNS (preko UDP, imena domaćina);", "example_upstream_dot": "šifrovano <0>DNS-over-TLS;", "example_upstream_doh": "šifrovano <0>DNS-over-HTTPS;", - "example_upstream_doq": "šifrovano <0>DNS-over-QUIC (eksperimentalno);", + "example_upstream_doq": "šifrovano <0>DNS-over-QUIC;", "example_upstream_sdns": "<0>DNS brojeve za <1>DNSCrypt ili <2>DNS-over-HTTPS razrešivače;", "example_upstream_tcp": "uobičajeni DNS (preko TCP);", "example_upstream_tcp_hostname": "uobičajen DNS (preko TCP, imena domaćina);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Upstream serveri su uspešno sačuvani", "dns_test_ok_toast": "Dati DNS serveri rade ispravno", "dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli", + "dns_test_warning_toast": "Apstrim \"{{key}}\" ne odgovara na zahteve za testiranje i možda neće raditi kako treba", "unblock": "Odblokiraj", "block": "Blokiraj", "disallow_this_client": "Zabrani ovaj klijent", @@ -362,15 +364,15 @@ "encryption_config_saved": "Konfiguracija šifrovanja je sačuvana", "encryption_server": "Ime servera", "encryption_server_enter": "Unesite vaše ime domena", - "encryption_server_desc": "Da biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL certifikatom ili džoker certifikatom. Ako polje nije postavljeno, prihvatiće TLS veze za bilo koji domen.", + "encryption_server_desc": "Ako je podešen, AdGuard Home otkriva ID-ove klijenta, odgovara na DDR upite i izvršava dodatne provere valjanosti veze. Ako se ne postave, ove funkcije su onemogućene. Mora se podudarati sa DNS imenima u certifikatu.", "encryption_redirect": "Automatski preusmeri na HTTPS", "encryption_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.", "encryption_https": "HTTPS port", "encryption_https_desc": "Ako je HTTPS port konfigurisan, AdGuard Home administratorskom okruženju će se moći pristupati preko HTTPS, a to će takođe omogućiti DNS-over-HTTPS na '/dns-query' lokaciji.", "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokretati DNS-over-TLS server na ovom portu.", - "encryption_doq": "DNS-over-QUIC port (eksperimentalno)", - "encryption_doq_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokrenuti DNS-over-QUIC server na tom portu. To je eksperiment i možda neće biti stabilno. Takođe, u ovom trenutku ne postoji puno klijenata koji ovo podržavaju.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokrenuti DNS-over-QUIC server na tom portu.", "encryption_certificates": "Sertifikati", "encryption_certificates_desc": "Da biste koristili šifrovanje, morate obezbediti važeći lanac SSL sertifikata za vaš domen. Besplatan sertifikat možete nabaviti na <0>{{link}} ili ga možete kupiti od nekog od pouzdanih izdavalaca sertifikata.", "encryption_certificates_input": "Kopirajte/nalepite vaše PEM-kodirane sertifikate ovde.", @@ -445,7 +447,7 @@ "access_blocked_title": "Blokirani domeni", "access_blocked_desc": "Da ne bude zabune sa filterima. AdGuard Home odustaje od DNS upita koji se podudaraju sa ovim domenima, a ovi upiti se čak i ne pojavljuju u evidenciji upita. Možete da navedete tačna imena domena, džoker znakove ili pravila URL filtera, npr. \"example.org\", \"*.example.org\" ili \"|| example.org^\" dopisno.", "access_settings_saved": "Postavke pristupa su uspešno sačuvane", - "updates_checked": "Ažuriranja su uspešno proverena", + "updates_checked": "Dostupna je nova verzija AdGuard Home-a", "updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju", "check_updates_now": "Proveri da li postoje ispravke", "dns_privacy": "DNS privatnost", diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json index a107e8c7..5fc3a4cc 100644 --- a/client/src/__locales/sv.json +++ b/client/src/__locales/sv.json @@ -47,6 +47,7 @@ "form_error_server_name": "Ogiltigt servernamn", "form_error_subnet": "Subnätet \"{{cidr}}\" innehåller inte IP-adressen \"{{ip}}\"", "form_error_positive": "Måste vara större än noll", + "form_error_gateway_ip": "Lease kan inte ha IP-adressen för gatewayen", "out_of_range_error": "Måste vara utanför intervallet \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Måste vara lägre än starten på intervallet", "greater_range_start_error": "Måste vara högre än starten på intervallet", @@ -213,7 +214,7 @@ "example_upstream_udp": "vanlig DNS (över UDP, värdnamn);", "example_upstream_dot": "krypterat <0>DNS-over-TLS", "example_upstream_doh": "krypterat <0>DNS-over-HTTPS", - "example_upstream_doq": "krypterat <0>DNS-over-QUIC (experimentell);", + "example_upstream_doq": "krypterat <0>DNS-over-QUIC;", "example_upstream_sdns": "Du kan använda <0>DNS-stamps för <1>DNSCrypt eller <2>DNS-over-HTTPS-resolvers", "example_upstream_tcp": "vanlig DNS (över UDP)", "example_upstream_tcp_hostname": "vanlig DNS (över TCP, värdnamn);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Sparade uppströms dns-servrar", "dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt", "dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte användas. Var snäll och kolla att du skrivit in rätt", + "dns_test_warning_toast": "Uppströms \"{{key}}\" svarar inte på testförfrågningar och kanske inte fungerar korrekt", "unblock": "Avblockera", "block": "Blockera", "disallow_this_client": "Tillåt inte den här klienten", @@ -362,15 +364,15 @@ "encryption_config_saved": "Krypteringsinställningar sparade", "encryption_server": "Servernamn", "encryption_server_enter": "Skriv in ditt domännamn", - "encryption_server_desc": "För att kunna använda HTTPS måste du ange servernamnet som matchar ditt SSL-certifikat eller jokerteckencertifikat. Om fältet inte är inställt kommer det att acceptera TLS-anslutningar för alla domäner.", + "encryption_server_desc": "För att använda HTTPS behöver du skriva in servernamnet som stämmer överens med ditt SSL-certifikat.", "encryption_redirect": "Omdirigera till HTTPS automatiskt", "encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.", "encryption_https": "HTTPS-port", "encryption_https_desc": "Om en HTTPS-port är inställd kommer gränssnittet till AdGuard Home administrering att kunna nås via HTTPS och kommer också att erbjuda DNS-over-HTTPS på '/dns-query' plats.", "encryption_dot": "DNS-över-TLS port", "encryption_dot_desc": "Om den här porten ställs in kommer AdGuard Home att använda DNS-over-TLS-server på porten.", - "encryption_doq": "DNS-over-QUIC port (experimentell)", - "encryption_doq_desc": "Om denna port är konfigurerad kommer AdGuard Home att köra en DNS-over-QUIC-server på denna port. Det är experimentellt och kanske inte är tillförlitligt. Dessutom finns det inte så många klienter som stödjer det för tillfället.", + "encryption_doq": "DNS-over-QUIC port", + "encryption_doq_desc": "Om denna port är konfigurerad kommer AdGuard Home att köra en DNS-over-QUIC-server på denna port. ", "encryption_certificates": "Certifikat", "encryption_certificates_desc": "För att använda kryptering måste du ange ett giltigt SSL-certifikat för din domän. Du kan skaffa ett certifikat gratis på <0>{{link}} eller köpa ett från någon av de godkända certifikatutfärdare.", "encryption_certificates_input": "Kopiera/klistra in dina PEM-kodade certifikat här.", @@ -445,7 +447,7 @@ "access_blocked_title": "Blockerade domäner", "access_blocked_desc": "Ej att förväxla med filter. AdGuard Home kastar DNS-frågor som matchar dessa domäner, och dessa frågor visas inte ens i frågeloggen. Du kan ange exakta domännamn, jokertecken eller URL-filterregler, t.ex. \"example.org\", \"*.example.org\" eller \"||example.org^\" på motsvarande sätt.", "access_settings_saved": "Åtkomstinställningar sparade", - "updates_checked": "Sökning efter uppdateringar genomförd", + "updates_checked": "En ny version av AdGuard Home är tillgänglig\n", "updates_version_equal": "AdGuard Home är uppdaterat", "check_updates_now": "Sök efter uppdateringar nu", "dns_privacy": "DNS-Integritet", @@ -612,7 +614,7 @@ "ttl_cache_validation": "Minsta cache TTL-värde måste vara mindre än eller lika med maxvärdet", "cache_optimistic": "Optimistisk cachning", "cache_optimistic_desc": "Få AdGuard Home att svara från cachen även när posterna har gått ut och försök även uppdatera dem.", - "filter_category_general": "General", + "filter_category_general": "Allmänt", "filter_category_security": "säkerhet", "filter_category_regional": "Regional", "filter_category_other": "Övrigt", diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index f26e0011..e4415e33 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -47,6 +47,7 @@ "form_error_server_name": "Geçersiz sunucu adı", "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor", "form_error_positive": "0'dan büyük olmalıdır", + "form_error_gateway_ip": "Kiralama, ağ geçidinin IP adresine sahip olamaz", "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır", "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır", "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır", @@ -108,7 +109,7 @@ "privacy_policy": "Gizlilik Politikası", "enable_protection": "Korumayı etkinleştir", "enabled_protection": "Koruma etkileştirildi", - "disable_protection": "Korumayı durdur", + "disable_protection": "Korumayı devre dışı bırak", "disabled_protection": "Koruma durduruldu", "refresh_statics": "İstatistikleri yenile", "dns_query": "DNS Sorguları", @@ -170,7 +171,7 @@ "disabled_safe_search_toast": "Güvenli Arama devre dışı bırakıldı", "enabled_save_search_toast": "Güvenli Arama etkinleştirildi", "enabled_table_header": "Etkin", - "name_table_header": "İsim", + "name_table_header": "Ad", "list_url_table_header": "Liste URL'si", "rules_count_table_header": "Kural sayısı", "last_time_updated_table_header": "Son güncelleme zamanı", @@ -185,7 +186,7 @@ "add_blocklist": "Engel listesi ekle", "add_allowlist": "İzin listesi ekle", "cancel_btn": "İptal", - "enter_name_hint": "İsim girin", + "enter_name_hint": "Ad girin", "enter_url_or_path_hint": "Listenin URL adresini veya dosya yolunu girin", "check_updates_btn": "Güncellemeleri denetle", "new_blocklist": "Yeni engel listesi", @@ -213,7 +214,7 @@ "example_upstream_udp": "normal DNS (UDP üzerinden, ana makine adı);", "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS;", "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS;", - "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC (deneysel);", + "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC;", "example_upstream_sdns": "<1>DNSCrypt veya <2>DNS-over-HTTPS çözümleyicileri için <0>DNS Damgaları;", "example_upstream_tcp": "normal DNS (TCP üzerinden);", "example_upstream_tcp_hostname": "normal DNS (TCP üzerinden, ana makine adı);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi", "dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor", "dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun", + "dns_test_warning_toast": "Üst kaynak \"{{key}}\", test isteklerine yanıt vermiyor ve düzgün çalışmayabilir", "unblock": "Engeli kaldır", "block": "Engelle", "disallow_this_client": "Bu istemciye izin verme", @@ -238,7 +240,7 @@ "empty_response_status": "Boş", "show_all_filter_type": "Tümünü göster", "show_filtered_type": "Filtrelenenleri göster", - "no_logs_found": "Günlük kaydı bulunamadı", + "no_logs_found": "Günlük bulunamadı", "refresh_btn": "Yenile", "previous_btn": "Önceki", "next_btn": "Sonraki", @@ -336,16 +338,16 @@ "install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu ayarlanamaz. Bu durumda, AdGuard Home'u <0>DHCP sunucusu olarak ayarlamak yardımcı olabilir. Aksi takdirde, yönlendirici modeliniz için DNS sunucularını nasıl ayarlayacağınız konusunda yönlendirici kılavuzuna bakmalısınız.", "install_devices_windows_list_1": "Başlat menüsünden veya Windows araması aracılığıyla Denetim Masası'nı açın.", "install_devices_windows_list_2": "Ağ ve İnternet kategorisine girin ve ardından Ağ ve Paylaşım Merkezi'ne girin.", - "install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin'e\" tıklayın.", + "install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin\" öğesine tıklayın.", "install_devices_windows_list_4": "Kullandığınız aktif bağlantının üzerine sağ tıklayın ve Özellikler öğesine tıklayın.", - "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler'e tıklayın.", + "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler öğesine tıklayın.", "install_devices_windows_list_6": "\"Aşağıdaki DNS sunucu adreslerini kullan\"ı seçin ve AdGuard Home sunucu adreslerinizi girin.", - "install_devices_macos_list_1": "Apple simgesine tıklayın ve Sistem Tercihleri'ne gidin.", - "install_devices_macos_list_2": "Ağ'a tıklayın.", + "install_devices_macos_list_1": "Apple simgesine tıklayın ve Sistem Tercihleri öğesine gidin.", + "install_devices_macos_list_2": "Ağ öğesine tıklayın.", "install_devices_macos_list_3": "Listedeki ilk bağlantıyı seçin ve Gelişmiş öğesine tıklayın.", "install_devices_macos_list_4": "DNS sekmesini seçin ve AdGuard Home sunucunuzun adreslerini girin.", "install_devices_android_list_1": "Android Menüsü ana ekranından Ayarlar'a dokunun.", - "install_devices_android_list_2": "Menüde bulunan Wi-Fi seçeneğine dokunun. Mevcut tüm ağlar listelenecektir (mobil ağlar için özel DNS sunucusu ayarlanamaz).", + "install_devices_android_list_2": "Menüde bulunan Wi-Fi öğesine dokunun. Mevcut tüm ağlar listelenecektir (mobil ağlar için özel DNS sunucusu ayarlanamaz).", "install_devices_android_list_3": "Bağlı olduğunuz ağın üzerine basılı tutun ve Ağı Değiştir'e dokunun.", "install_devices_android_list_4": "Bazı cihazlarda, diğer ayarları görmek için \"Gelişmiş\" seçeneğini seçmeniz gerekebilir. Android DNS ayarlarınızı yapmak için IP ayarlarını DHCP modundan Statik moda almanız gerekecektir.", "install_devices_android_list_5": "DNS 1 ve DNS 2 değerlerini AdGuard Home sunucunuzun adresleriyle değiştirin.", @@ -362,15 +364,15 @@ "encryption_config_saved": "Şifreleme yapılandırması kaydedildi", "encryption_server": "Sunucu adı", "encryption_server_enter": "Alan adınızı girin", - "encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla veya joker sertifikanızla eşleşen sunucu adını girmeniz gerekir. Bu alan ayarlanmazsa, herhangi bir alan adının TLS bağlantılarını kabul eder.", + "encryption_server_desc": "Ayarlanırsa, AdGuard Home ClientID'leri algılar, DDR sorgularına yanıt verir ve ek bağlantı doğrulamaları gerçekleştirir. Ayarlanmazsa, bu özellikler devre dışı bırakılır. Sertifikadaki DNS Adlarından biriyle eşleşmelidir.", "encryption_redirect": "Otomatik olarak HTTPS'e yönlendir", "encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.", "encryption_https": "HTTPS bağlantı noktası", "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlayacaktır.", "encryption_dot": "DNS-over-TLS bağlantı noktası", "encryption_dot_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-TLS sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır.", - "encryption_doq": "DNS-over-QUIC bağlantı noktası (deneysel)", - "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-QUIC sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır. Bu özellik deneme aşamasındadır ve güvenilir olmayabilir. Ayrıca, şu anda bu özelliği destekleyen çok fazla istemci yok.", + "encryption_doq": "DNS-over-QUIC bağlantı noktası", + "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, bu bağlantı noktasında bir DNS-over-QUIC sunucusu çalıştıracaktır.", "encryption_certificates": "Sertifikalar", "encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınıza geçerli bir SSL sertifika zinciri sağlamanız gerekir. <0>{{link}} adresinden ücretsiz bir sertifika alabilir veya güvenilir Sertifika Yetkililerinden satın alabilirsiniz.", "encryption_certificates_input": "PEM biçimindeki sertifikalarınızı kopyalayıp buraya yapıştırın.", @@ -395,7 +397,7 @@ "form_error_equal": "Aynı olmamalıdır", "form_error_password": "Parolalar uyuşmuyor", "reset_settings": "Ayarları sıfırla", - "update_announcement": "AdGuard Home {{version}} sürümü mevcut! Daha fazla bilgi için <0>buraya tıklayın.", + "update_announcement": "AdGuard Home {{version}} sürümü artık mevcut! Daha fazla bilgi için <0>buraya tıklayın.", "setup_guide": "Kurulum Rehberi", "dns_addresses": "DNS adresleri", "dns_start": "DNS sunucusu başlatılıyor", @@ -412,7 +414,7 @@ "settings_global": "Genel", "settings_custom": "Özel", "table_client": "İstemci", - "table_name": "İsim", + "table_name": "Ad", "save_btn": "Kaydet", "client_add": "İstemci Ekle", "client_new": "Yeni İstemci", @@ -426,7 +428,7 @@ "form_enter_id": "Tanımlayıcı girin", "form_add_id": "Tanımlayıcı ekle", "form_client_name": "İstemci ismi girin", - "name": "İsim", + "name": "Ad", "client_global_settings": "Genel ayarları kullan", "client_deleted": "\"{{key}}\" istemcisi başarıyla silindi", "client_added": "\"{{key}}\" istemcisi başarıyla eklendi", @@ -445,7 +447,7 @@ "access_blocked_title": "İzin verilmeyen alan adları", "access_blocked_desc": "Bu işlem filtrelerle ilgili değildir. AdGuard Home, bu alan adlarından gelen DNS sorgularını yanıtsız bırakır ve bu sorgular sorgu günlüğünde görünmez. Tam alan adlarını, joker karakterleri veya URL filtre kurallarını belirtebilirsiniz, ör. \"example.org\", \"*.example.org\" veya \"||example.org^\".", "access_settings_saved": "Erişim ayarları başarıyla kaydedildi!", - "updates_checked": "Güncelleme kontrolü başarılı", + "updates_checked": "AdGuard Home'un yeni bir sürümü mevcut", "updates_version_equal": "AdGuard Home yazılımı güncel durumda", "check_updates_now": "Güncellemeleri şimdi denetle", "dns_privacy": "DNS Gizliliği", @@ -453,7 +455,7 @@ "setup_dns_privacy_2": "<0>DNS-over-HTTPS: <1>{{address}} dizesini kullan.", "setup_dns_privacy_3": "<0>İşte, kullanabileceğiniz yazılımların bir listesi.", "setup_dns_privacy_4": "Bir iOS 14 veya macOS Big Sur cihazında, DNS ayarlarına DNS-over-HTTPS veya DNS-over-TLS sunucuları ekleyen özel '.mobileconfig' dosyasını indirebilirsiniz.", - "setup_dns_privacy_android_1": "Android 9, yerel olarak DNS-over-TLS protokolünü destekler. Yapılandırmak için Ayarlar → Ağ ve İnternet → Gelişmiş → Özel DNS seçeneğine gidin ve alan adınızı girin.", + "setup_dns_privacy_android_1": "Android 9, yerel olarak DNS-over-TLS protokolünü destekler. Yapılandırmak için Ayarlar → Ağ ve İnternet → Gelişmiş → Özel DNS öğesine gidin ve alan adınızı girin.", "setup_dns_privacy_android_2": "<0>Android için AdGuard, <1>DNS-over-HTTPS ve <1>DNS-over-TLS protokolünü destekler.", "setup_dns_privacy_android_3": "<0>Intra Android'e <1>DNS-over-HTTPS protokol desteğini ekler.", "setup_dns_privacy_ios_1": "<0>DNSCloak, <1>DNS-over-HTTPS protokolünü destekler, ancak kendi sunucunuzu kullanacak şekilde yapılandırmak için bir <2>DNS Damgası oluşturmanız gerekir.", diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json index 9fcbd460..d89c1da4 100644 --- a/client/src/__locales/uk.json +++ b/client/src/__locales/uk.json @@ -7,16 +7,16 @@ "load_balancing": "Балансування навантаження", "load_balancing_desc": "Запитувати один сервер за раз. AdGuard Home використовуватиме зважений випадковий алгоритм для вибору сервера, щоб найшвидший сервер використовувався частіше.", "bootstrap_dns": "Bootstrap DNS-сервери", - "bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для пошуку IP-адреси DoH/DoT серверів, які ви встановили.", + "bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для вирішення IP-адрес встановлених серверів DoH/DoT.", "local_ptr_title": "Приватні сервери для зворотного DNS", - "local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери, використовуючи rDNS, використовуються для отримання доменних імен клієнтів у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, буде використовуватись системний DNS-сервер.", - "local_ptr_default_resolver": "AdGuard Home усталено використовує такі зворотні DNS-резолвери: {{ip}}.", - "local_ptr_no_default_resolver": "AdGuard Home не зміг визначити приватні реверсивні DNS-резолвери, що були б придатними для цієї системи.", + "local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери використовуються для вирішення PTR-запитів для адрес у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, AdGuard Home буде усталено використовувати системний DNS-сервер.", + "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_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": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі завдяки DHCP, /etc/hosts тощо.", "check_dhcp_servers": "Перевірити DHCP-сервери", "save_config": "Зберегти конфігурацію", "enabled_dhcp": "DHCP-сервер увімкнено", @@ -47,6 +47,7 @@ "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": "Має бути більшим за початкову адресу", @@ -60,7 +61,7 @@ "dhcp_form_range_end": "Кінець діапазону", "dhcp_form_lease_title": "Час оренди DHCP (в секундах)", "dhcp_form_lease_input": "Тривалість оренди", - "dhcp_interface_select": "Оберіть інтерфейс DHCP", + "dhcp_interface_select": "Вибрати DHCP-інтерфейс", "dhcp_hardware_address": "Апаратна адреса", "dhcp_ip_addresses": "IP-адреси", "ip": "IP", @@ -77,7 +78,7 @@ "dhcp_add_static_lease": "Додати статичну оренду", "dhcp_reset_leases": "Скинути всі аренди", "dhcp_reset_leases_confirm": "Ви дійсно хочете скинути усі аренди?", - "dhcp_reset_leases_success": "Аренди DHCP успішно скинуто", + "dhcp_reset_leases_success": "Оренду DHCP успішно скинуто", "dhcp_reset": "Ви дійсно хочете скинути DHCP-конфігурацію?", "country": "Країна", "city": "Місто", @@ -105,7 +106,7 @@ "copyright": "Авторське право", "homepage": "Домашня сторінка", "report_an_issue": "Повідомити про проблему", - "privacy_policy": "Політика приватності", + "privacy_policy": "Політика конфіденційності", "enable_protection": "Увімкнути захист", "enabled_protection": "Захист увімкнено", "disable_protection": "Вимкнути захист", @@ -117,11 +118,11 @@ "stats_adult": "Заблоковано вебсайтів для дорослих", "stats_query_domain": "Найчастіші запити доменів", "for_last_24_hours": "за останні 24 години", - "for_last_days": "за останній день", + "for_last_days": "за останній {{count}} день", "for_last_days_plural": "за останні {{count}} днів", "stats_disabled": "Статистику вимкнено. Ви можете увімкнути її на <0>сторінці налаштувань.", "stats_disabled_short": "Статистику вимкнено", - "no_domains_found": "Доменів не знайдено", + "no_domains_found": "Не знайдено жодного домену", "requests_count": "Кількість запитів", "top_blocked_domains": "Найчастіше блоковані домени", "top_clients": "Найактивніші клієнти", @@ -131,7 +132,7 @@ "number_of_dns_query_days_plural": "Кількість DNS-запитів, оброблених за останні {{count}} днів", "number_of_dns_query_24_hours": "Кількість DNS-запитів, оброблених за останні 24 години", "number_of_dns_query_blocked_24_hours": "Кількість DNS-запитів, заблокованих фільтрами і списками блокування hosts", - "number_of_dns_query_blocked_24_hours_by_sec": "Кількість DNS-запитів, заблокованих модулем безпеки перегляду AdGuard", + "number_of_dns_query_blocked_24_hours_by_sec": "Кількість DNS-запитів, заблокованих модулем «Безпека перегляду» AdGuard", "number_of_dns_query_blocked_24_hours_adult": "Кількість заблокованих вебсайтів для дорослих", "enforced_save_search": "Примусовий безпечний пошук", "number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук", @@ -139,10 +140,10 @@ "average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах", "block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів", "filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі Фільтри.", - "use_adguard_browsing_sec": "Використовувати Безпечну навігацію AdGuard", - "use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи додано домен до списку веб-служби безпечного перегляду браузера. Він використовуватиме API для перевірки — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.", - "use_adguard_parental": "Використовувати вебсервіс Батьківського контролю AdGuard", - "use_adguard_parental_hint": "AdGuard Home перевірить, чи містить домен матеріали для дорослих. Він використовує то же API, що й Безпечна навігація AdGuard.", + "use_adguard_browsing_sec": "Використовувати вебслужбу «Безпека перегляду» AdGuard", + "use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи підлягає домен блокуванню завдяки вебслужбі «Безпека перегляду». Для перевірки буде використано безпечний API — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.", + "use_adguard_parental": "Використовувати вебслужбу «Батьківський контроль» AdGuard", + "use_adguard_parental_hint": "AdGuard Home перевірить, чи містить домен матеріали для дорослих. Буде використано той же безпечний API, що й для «Безпеки перегляду» AdGuard.", "enforce_safe_search": "Використовувати Безпечний пошук", "enforce_save_search_hint": "AdGuard Home може примусово застосовувати безпечний пошук в таких пошукових системах: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Сервери не вказано", @@ -165,8 +166,8 @@ "enabled_filtering_toast": "Фільтрування увімкнено", "disabled_safe_browsing_toast": "Безпечний перегляд вимкнено", "enabled_safe_browsing_toast": "Безпечний перегляд увімкнено", - "disabled_parental_toast": "Батьківський контроль вимкнено", - "enabled_parental_toast": "Батьківський контроль увімкнено", + "disabled_parental_toast": "«Батьківський контроль» вимкнено", + "enabled_parental_toast": "«Батьківський контроль» увімкнено", "disabled_safe_search_toast": "Безпечний пошук вимкнено", "enabled_save_search_toast": "Безпечний пошук увімкнено", "enabled_table_header": "Увімкнено", @@ -193,7 +194,7 @@ "edit_blocklist": "Змінити список блокування", "edit_allowlist": "Змінити список дозволів", "choose_blocklist": "Виберіть списки блокування", - "choose_allowlist": "Обрати списки дозволених сайтів", + "choose_allowlist": "Виберіть списки дозволів", "enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.", "enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.", "form_error_url_format": "Неправильний формат URL", @@ -213,14 +214,15 @@ "example_upstream_udp": "звичайний DNS (поверх UDP, з назвою вузла);", "example_upstream_dot": "зашифрований <0>DNS-over-TLS;", "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS;", - "example_upstream_doq": "зашифрований <0>DNS-over-QUIC (експериментальний);", - "example_upstream_sdns": "<0>DNS Stamps для <1>DNSCrypt або <2>DNS-over-HTTPS серверів;", + "example_upstream_doq": "зашифрований <0>DNS-over-QUIC;", + "example_upstream_sdns": "<0>DNS Stamps для <1>DNSCrypt- або <2>DNS-over-HTTPS-вирішувачів;", "example_upstream_tcp": "звичайний DNS (через TCP);", "example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);", "all_lists_up_to_date_toast": "Всі списки вже оновлені", - "updated_upstream_dns_toast": "DNS-сервери оновлено", + "updated_upstream_dns_toast": "DNS-сервери успішно збережено", "dns_test_ok_toast": "Вказані DNS сервери працюють правильно", "dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення", + "dns_test_warning_toast": "Upstream «{{key}}» не відповідає на тестові запити та може працювати не правильно", "unblock": "Дозволити", "block": "Заборонити", "disallow_this_client": "Заборонити цього клієнта", @@ -245,7 +247,7 @@ "loading_table_status": "Завантаження...", "page_table_footer_text": "Сторінка", "rows_table_footer_text": "рядків", - "updated_custom_filtering_toast": "Власні правила фільтрування збережено", + "updated_custom_filtering_toast": "Власні правила фільтрування успішно збережено", "rule_removed_from_custom_filtering_toast": "Правило вилучено з власних правил фільтрування: {{rule}}", "rule_added_to_custom_filtering_toast": "Правило додано до власних правил фільтрування: {{rule}}", "query_log_response_status": "Стан: {{value}}", @@ -351,7 +353,7 @@ "install_devices_android_list_5": "Змініть встановлені значення DNS 1 і DNS 2 на адреси вашого домашнього сервера AdGuard.", "install_devices_ios_list_1": "На головному екрані торкніться Налаштування.", "install_devices_ios_list_2": "Виберіть Wi-Fi у меню ліворуч (неможливо налаштувати DNS для мобільних мереж).", - "install_devices_ios_list_3": "Натисніть на назву поточно активної мережі.", + "install_devices_ios_list_3": "Натисніть на назву поточної активної мережі.", "install_devices_ios_list_4": "У полі DNS введіть адреси вашого сервера AdGuard Home.", "get_started": "Розпочати", "next": "Наступні", @@ -362,17 +364,17 @@ "encryption_config_saved": "Конфігурацію шифрування збережено", "encryption_server": "Назва сервера", "encryption_server_enter": "Введіть ваше доменне ім'я", - "encryption_server_desc": "Для використання HTTPS вам потрібно ввести назву сервера, який відповідає вашому SSL-сертифікату або сертифікату з підтримкою піддоменів. Якщо значення не вказано, то сервер буде приймати TLS-з'єднання для будь-якого домену.", + "encryption_server_desc": "Якщо встановлено, AdGuard Home розпізнає ClientID, відповідає на DDR-запити та додатково перевіряє з'єднання. Якщо не встановлено, то цей функціонал вимкнено. Мусить відповідати одному з параметрів DNS Names в сертифікаті.", "encryption_redirect": "Автоматично перенаправляти на HTTPS", "encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.", "encryption_https": "Порт HTTPS", "encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.", "encryption_dot": "Порт DNS-over-TLS", "encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.", - "encryption_doq": "Порт DNS-over-QUIC (експериментальний)", - "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-QUIC. Це експериментально і може бути ненадійним. Крім того, зараз не так багато клієнтів, які це підтримують.", + "encryption_doq": "Порт DNS-over-QUIC", + "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на ньому сервер DNS-over-QUIC.", "encryption_certificates": "Сертифікати", - "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безкоштовний сертифікат на <0>{{link}} або придбати його в одному з надійних Центрів Сертифікації.", + "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безплатний сертифікат на <0>{{link}} або придбати його в одному з надійних Центрів Сертифікації.", "encryption_certificates_input": "Скопіюйте/вставте сюди свої кодовані PEM сертифікати.", "encryption_status": "Статус", "encryption_expire": "Закінчується", @@ -445,7 +447,7 @@ "access_blocked_title": "Заборонені домени", "access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.", "access_settings_saved": "Налаштування доступу успішно збережено", - "updates_checked": "Оновлення успішно перевірені", + "updates_checked": "Доступна нова версія AdGuard Home", "updates_version_equal": "AdGuard Home останньої версії", "check_updates_now": "Перевірити наявність оновлень", "dns_privacy": "Конфіденційність DNS", @@ -514,7 +516,7 @@ "statistics_clear": "Очистити статистику", "statistics_clear_confirm": "Ви впевнені, що хочете очистити статистику?", "statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені", - "statistics_cleared": "Статистика успішно очищена", + "statistics_cleared": "Статистику успішно очищено", "statistics_enable": "Увімкнути статистику", "interval_hours": "{{count}} година", "interval_hours_plural": "{{count}} годин(и)", @@ -552,16 +554,16 @@ "fastest_addr": "Найшвидша IP-адреса", "fastest_addr_desc": "Опитати всі DNS-сервери й повернути найшвидшу IP-адресу серед усіх наданих. Це сповільнить швидкість DNS-запитів, оскільки AdGuard Home повинен буде чекати відповіді усіх DNS-серверів, але водночас може покращити якість з'єднання.", "autofix_warning_text": "Якщо ви натиснете «Виправити», AdGuard Home налаштує вашу систему на використання DNS-сервера AdGuard Home.", - "autofix_warning_list": "Це виконає наступні завдання: <0>Деактивує систему DNSStubListener <0>Змінить адресу DNS сервера на 127.0.0.1 <0>Замінить символічне посилання /etc/resolv.conf на /run/systemd/resolve/resolv.conf <0>Зупинить DNSStubListener (перезапустить сервіс systemd-resolved)", + "autofix_warning_list": "Будуть виконані такі дії: <0>Деактивація системи DNSStubListener <0>Зміна адреси DNS-сервера на «127.0.0.1» <0>Заміна символічного посилання «/etc/resolv.conf» на «/run/systemd/resolve/resolv.conf» <0>Зупинка DNSStubListener (перезапуск системної служби systemd-resolved)", "autofix_warning_result": "В результаті буде усталено, що усі DNS-запити вашої системи будуть опрацьовані AdGuard Home.", "tags_title": "Теги", "tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше.", "form_select_tags": "Виберіть теги клієнта", - "check_title": "Перевірте фільтрування", + "check_title": "Перевірити фільтрування", "check_desc": "Перевірити чи фільтрується назва вузла.", "check": "Перевірити", "form_enter_host": "Введіть назву вузла", - "filtered_custom_rules": "Відфільтровано за власними правилами фільтрування", + "filtered_custom_rules": "Відфільтровано завдяки власним правилам фільтрування", "choose_from_list": "Виберіть зі списку", "add_custom_list": "Додати власний список", "host_whitelisted": "Вузол додано до списку дозволів", @@ -585,14 +587,14 @@ "list_updated": "{{count}} список оновлено", "list_updated_plural": "{{count}} списки оновлено", "dnssec_enable": "Увімкнути DNSSEC", - "dnssec_enable_desc": "Встановити прапорець DNSSEC для вихідних DNS запитів та перевірити результат (потрібен розпізнавач з підтримкою DNSSEC).", + "dnssec_enable_desc": "Увімкнути DNSSEC для вихідних DNS-запитів та перевірити результат (потрібен вирішувач з підтримкою DNSSEC).", "validated_with_dnssec": "Засвідчено DNSSEC", "all_queries": "Усі запити", "show_blocked_responses": "Заблоковані", "show_whitelisted_responses": "Дозволені", "show_processed_responses": "Оброблені", - "blocked_safebrowsing": "Заблоковано Безпечним переглядом", - "blocked_adult_websites": "Заблоковано Батьківським контролем", + "blocked_safebrowsing": "Заблоковано модулем «Безпека перегляду»", + "blocked_adult_websites": "Заблоковано «Батьківським контролем»", "blocked_threats": "Заблоковано загроз", "allowed": "Дозволено", "filtered": "Відфільтровано", diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index 7c9c2fd4..3000a29a 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -47,6 +47,7 @@ "form_error_server_name": "Tên máy chủ không hợp lệ", "form_error_subnet": "Mạng con \"{{cidr}}\" không chứa địa chỉ IP \"{{ip}}\"", "form_error_positive": "Phải lớn hơn 0", + "form_error_gateway_ip": "Cho thuê không thể có địa chỉ IP của cổng", "out_of_range_error": "Phải nằm ngoài phạm vi \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "Phải thấp hơn khởi động phạm vi", "greater_range_start_error": "Phải lớn hơn khoảng bắt đầu", @@ -213,14 +214,15 @@ "example_upstream_udp": "DNS thông thường (qua UDP, tên máy chủ);", "example_upstream_dot": "được mã hoá <0>DNS-over-TLS;", "example_upstream_doh": "được mã hoá <0>DNS-over-HTTPS;", - "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC (thử nghiệm);", + "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC;", "example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps for <1>DNSCrypt hoặc <2>DNS-over-HTTPS ", "example_upstream_tcp": "DNS thông thường(dùng TCP);", "example_upstream_tcp_hostname": "DNS thông thường (qua TCP, tên máy chủ);", "all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất", "updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công", "dns_test_ok_toast": "Máy chủ DNS có thể sử dụng", - "dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại", + "dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại", + "dns_test_warning_toast": "Ngược lại \"{{key}}\" không phản hồi các yêu cầu kiểm tra và có thể không hoạt động bình thường", "unblock": "Bỏ chặn", "block": "Chặn", "disallow_this_client": "Không cho phép client này", @@ -362,15 +364,15 @@ "encryption_config_saved": "Đã lưu cấu hình mã hóa", "encryption_server": "Tên máy chủ", "encryption_server_enter": "Nhập tên miền của bạn", - "encryption_server_desc": "Để sử dụng HTTPS, bạn cần nhập tên máy chủ phù hợp với chứng chỉ SSL của bạn. Nếu trường này bị bỏ trống, nó sẽ chấp nhận kết nối TLS với tất cả tên miền.", + "encryption_server_desc": "Nếu được đặt, AdGuard Home sẽ phát hiện ClientID, phản hồi các truy vấn DDR và thực hiện xác thực kết nối bổ sung. Nếu không được đặt, các tính năng này sẽ bị vô hiệu hóa. Phải khớp với một trong các Tên DNS trong chứng chỉ.", "encryption_redirect": "Tự động chuyển hướng đến HTTPS", "encryption_redirect_desc": "Nếu được chọn, AdGuard Home sẽ tự động chuyển hướng bạn từ địa chỉ HTTP sang địa chỉ HTTPS.", "encryption_https": "Cổng HTTPS", "encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.", "encryption_dot": "Cổng DNS-over-TLS", "encryption_dot_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS-over-TLS trên cổng này.", - "encryption_doq": "Cổng DNS-over-QUIC (thử nghiệm)", - "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. Đó là thử nghiệm và có thể không đáng tin cậy. Ngoài ra, không có quá nhiều khách hàng hỗ trợ nó vào lúc này.", + "encryption_doq": "Cổng DNS-over-QUIC", + "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. ", "encryption_certificates": "Chứng chỉ", "encryption_certificates_desc": "Để sử dụng mã hóa, bạn cần cung cấp chuỗi chứng chỉ SSL hợp lệ cho miền của mình. Bạn có thể nhận chứng chỉ miễn phí trên <0>{{link}} hoặc bạn có thể mua chứng chỉ từ một trong các Cơ Quan Chứng Nhận tin cậy.", "encryption_certificates_input": "Sao chép/dán chứng chỉ được mã hóa PEM của bạn tại đây.", @@ -445,7 +447,7 @@ "access_blocked_title": "Tên miền bị chặn", "access_blocked_desc": "Đừng nhầm lẫn điều này với các bộ lọc. AdGuard Home sẽ bỏ các truy vấn DNS với các tên miền này trong câu hỏi của truy vấn.", "access_settings_saved": "Cài đặt truy cập đã lưu thành công", - "updates_checked": "Đã kiểm tra thành công cập nhật", + "updates_checked": "Phiên bản mới của AdGuard Home có sẵn", "updates_version_equal": "AdGuard Home đã được cập nhật", "check_updates_now": "Kiểm tra cập nhật ngay bây giờ", "dns_privacy": "DNS Riêng Tư", diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index 4640432f..7c777c1d 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -47,6 +47,7 @@ "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": "必须大于范围起始值", @@ -213,7 +214,7 @@ "example_upstream_udp": "常规 DNS(通过 UDP、主机名);", "example_upstream_dot": "加密 <0>DNS-over-TLS;", "example_upstream_doh": "加密 <0>DNS-over-HTTPS;", - "example_upstream_doq": "加密 <0>DNS-over-QUIC(实验性的);", + "example_upstream_doq": "加密 <0>DNS-over-QUIC", "example_upstream_sdns": "<1>DNSCrypt 的 <0>DNS Stamps 或者 <2>DNS-over-HTTPS 解析器;", "example_upstream_tcp": "常规 DNS(基于 TCP );", "example_upstream_tcp_hostname": "常规 DNS(通过 TCP、主机名);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "上游服务器保存成功", "dns_test_ok_toast": "指定的 DNS 服务器现已正常运行", "dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确", + "dns_test_warning_toast": "上游 “{{key}}” 不响应测试请求,可能无法正常工作", "unblock": "放行", "block": "拦截", "disallow_this_client": "不允许这个客户端", @@ -351,6 +353,7 @@ "install_devices_android_list_5": "将 DNS 1 和 DNS 2 的值改为您的 AdGuard Home 服务器地址。", "install_devices_ios_list_1": "从主屏幕中点击「设置」。", "install_devices_ios_list_2": "从左侧目录中选择「无线局域网」(移动数据网络环境下不支持修改 DNS )。", + "install_devices_ios_list_3": "点击当前已连接网络的名称。", "install_devices_ios_list_4": "在 DNS 字段中输入您的 AdGuard Home 服务器地址。", "get_started": "开始配置", "next": "下一步", @@ -361,15 +364,15 @@ "encryption_config_saved": "加密配置已保存", "encryption_server": "服务器名称", "encryption_server_enter": "输入您的域名", - "encryption_server_desc": "为了使用 HTTPS,请您输入与 SSL 证书或通配证书相匹配的服务器名称。如此字段未设置,服务器将要为所有域名接受 TLS 连接。", + "encryption_server_desc": "设置后,AdGuard Home 检测客户端标识号,对 DDR 查询作出反应,以及进一步检查连接。在没有设置的情况下,该功能被禁用。必须与证书里的一个 DNS 名称相匹配。", "encryption_redirect": "HTTPS 自动重定向", "encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。", "encryption_https": "HTTPS 端口", "encryption_https_desc": "如果配置了 HTTPS 端口,AdGuard Home 管理界面将可以通过 HTTPS 访问,它还将在在 '/dns-query' 位置提供 DNS-over-HTTPS 。", "encryption_dot": "DNS-over-TLS 端口", "encryption_dot_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-TLS 服务器。", - "encryption_doq": "DNS-over-QUIC 端口(实验性的)", - "encryption_doq_desc": "如果配置了此端口,AdGuard Home将在此端口上运行一个DNS-over-QUIC服务器。这是实验性的,可能不可靠。而且,支持此特性的客户端并不多。", + "encryption_doq": "DNS-over-QUIC 端口", + "encryption_doq_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-QUIC 服务器。", "encryption_certificates": "证书", "encryption_certificates_desc": "为了使用加密,您需要为域提供有效的 SSL 证书链。您可以在 <0>{{link}} 上获得免费证书,也可以从受信任的证书颁发机构购买证书。", "encryption_certificates_input": "将您以 PEM 格式编码的证书复制粘贴到此处。", @@ -444,7 +447,7 @@ "access_blocked_title": "不允许的域名", "access_blocked_desc": "不要将此功能与过滤器混淆。AdGuard Home 将排除匹配这些网域的 DNS 查询,并且这些查询将不会在查询日志中显示。在此可以明确指定域名、通配符(wildcard)和网址过滤的规则,例如 \"example.org\"、\"*.example.org\" 或 \"||example.org^\"。", "access_settings_saved": "访问设置保存成功", - "updates_checked": "检查更新成功", + "updates_checked": "AdGuard Home 的新版本现在可用", "updates_version_equal": "AdGuard Home已经是最新版本", "check_updates_now": "立即检查更新", "dns_privacy": "DNS 隐私", diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 435d6b01..3f4cdef2 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -47,6 +47,7 @@ "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": "必須大於起始範圍", @@ -213,7 +214,7 @@ "example_upstream_udp": "常規 DNS(透過 UDP,主機名稱);", "example_upstream_dot": "加密的 <0>DNS-over-TLS;", "example_upstream_doh": "加密的 <0>DNS-over-HTTPS;", - "example_upstream_doq": "加密的 <0>DNS-over-QUIC(實驗性的);", + "example_upstream_doq": "加密的 <0>DNS-over-QUIC;", "example_upstream_sdns": "關於 <1>DNSCrypt 或 <2>DNS-over-HTTPS 解析器之 <0>DNS 戳記;", "example_upstream_tcp": "常規 DNS(透過 TCP);", "example_upstream_tcp_hostname": "常規 DNS(透過 TCP,主機名稱);", @@ -221,6 +222,7 @@ "updated_upstream_dns_toast": "上游的伺服器被成功地儲存", "dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作", "dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它", + "dns_test_warning_toast": "上游 “{{key}}” 不回應測試請求,可能無法正常工作", "unblock": "解除封鎖", "block": "封鎖", "disallow_this_client": "不允許此用戶端", @@ -362,15 +364,15 @@ "encryption_config_saved": "加密配置被儲存", "encryption_server": "伺服器名稱", "encryption_server_enter": "輸入您的域名", - "encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證或萬用字元憑證相符的伺服器名稱。如果此欄位未被設定,它將接受向任何網域的傳輸層安全性協定(TLS)連線。", + "encryption_server_desc": "如果被設定,AdGuard Home 檢測用戶端 IDs,回覆 DDR 查詢,並執行額外的連線驗證。如果未被設定,這些功能被禁用。必須與在該憑證裡的 DNS 名稱其中之一相符。", "encryption_redirect": "自動地重新導向到 HTTPS", "encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。", "encryption_https": "HTTPS 連接埠", "encryption_https_desc": "如果 HTTPS 連接埠被配置,AdGuard Home 管理員介面透過 HTTPS 將為可存取的,且它也將於 '/dns-query' 位置上提供 DNS-over-HTTPS。", "encryption_dot": "DNS-over-TLS 連接埠", "encryption_dot_desc": "如果該連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-TLS 伺服器。", - "encryption_doq": "DNS-over-QUIC 連接埠(實驗性的)", - "encryption_doq_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。它是實驗性的並可能為不可靠的。再者,此刻沒有太多支援它的用戶端。", + "encryption_doq": "DNS-over-QUIC 連接埠", + "encryption_doq_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。", "encryption_certificates": "憑證", "encryption_certificates_desc": "為了使用加密,您需要提供有效的安全通訊端層(SSL)憑證鏈結供您的網域。於 <0>{{link}} 上您可取得免費的憑證或您可從受信任的憑證授權單位之一購買它。", "encryption_certificates_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)憑證。", @@ -445,7 +447,7 @@ "access_blocked_title": "未被允許的網域", "access_blocked_desc": "不要把這個和過濾器混淆。AdGuard Home 排除與這些網域相符的 DNS 查詢,且這些查詢甚至不會出現在查詢記錄中。您可相應地明確指定確切的域名、萬用字元(wildcard)或網址過濾器的規則,例如,\"example.org\"、\"*.example.org\" 或 \"||example.org^\"。", "access_settings_saved": "存取設定被成功地儲存", - "updates_checked": "更新被成功地檢查", + "updates_checked": "AdGuard Home 的新版本為可用的", "updates_version_equal": "AdGuard Home 為最新的", "check_updates_now": "立即檢查更新", "dns_privacy": "DNS 隱私", @@ -630,6 +632,6 @@ "use_saved_key": "使用該先前已儲存的金鑰", "parental_control": "家長控制", "safe_browsing": "安全瀏覽", - "served_from_cache": "{{value}} (由快取提供)", + "served_from_cache": "{{value}} (由快取提供)", "form_error_password_length": "密碼必須為至少長 {{value}} 個字元" } diff --git a/client/src/actions/encryption.js b/client/src/actions/encryption.js index 36faf2ec..2f58abd3 100644 --- a/client/src/actions/encryption.js +++ b/client/src/actions/encryption.js @@ -24,6 +24,7 @@ export const getTlsStatus = () => async (dispatch) => { export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST'); export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE'); export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS'); +export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS'); export const setTlsConfig = (config) => async (dispatch, getState) => { dispatch(setTlsConfigRequest()); @@ -39,6 +40,12 @@ export const setTlsConfig = (config) => async (dispatch, getState) => { const response = await apiClient.setTlsConfig(values); response.certificate_chain = atob(response.certificate_chain); response.private_key = atob(response.private_key); + + const dnsStatus = await apiClient.getGlobalStatus(); + if (dnsStatus) { + dispatch(dnsStatusSuccess(dnsStatus)); + } + dispatch(setTlsConfigSuccess(response)); dispatch(addSuccessToast('encryption_config_saved')); redirectToCurrentProtocol(response, httpPort); diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 8e153224..e1b4e96c 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -314,13 +314,15 @@ export const testUpstream = ( const testMessages = Object.keys(upstreamResponse) .map((key) => { const message = upstreamResponse[key]; - if (message !== 'OK') { + if (message.startsWith('WARNING:')) { + dispatch(addErrorToast({ error: i18next.t('dns_test_warning_toast', { key }) })); + } else if (message !== 'OK') { dispatch(addErrorToast({ error: i18next.t('dns_test_not_ok_toast', { key }) })); } return message; }); - if (testMessages.every((message) => message === 'OK')) { + if (testMessages.every((message) => message === 'OK' || message.startsWith('WARNING:'))) { dispatch(addSuccessToast('dns_test_ok_toast')); } diff --git a/client/src/components/Filters/Rewrites/Table.js b/client/src/components/Filters/Rewrites/Table.js index 45638ec0..28009ca3 100644 --- a/client/src/components/Filters/Rewrites/Table.js +++ b/client/src/components/Filters/Rewrites/Table.js @@ -29,6 +29,8 @@ class Table extends Component { Header: this.props.t('actions_table_header'), accessor: 'actions', maxWidth: 100, + sortable: false, + resizable: false, Cell: (value) => (
; + const blockButton = ( + <> +
+ + + ); const blockForClientButton =
diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js index 63e73b5d..be01b2b7 100644 --- a/client/src/components/Settings/Dns/Upstream/Form.js +++ b/client/src/components/Settings/Dns/Upstream/Form.js @@ -149,7 +149,7 @@ const Form = ({ {' '} { const renderCopyright = () =>
{t('copyright')} © {getYear()}{' '} - AdGuard + AdGuard
; diff --git a/client/src/components/ui/Guide/Guide.js b/client/src/components/ui/Guide/Guide.js index 53cf82a7..64537320 100644 --- a/client/src/components/ui/Guide/Guide.js +++ b/client/src/components/ui/Guide/Guide.js @@ -38,7 +38,7 @@ const getDnsPrivacyList = () => [ components: [ { key: 0, - href: 'https://adguard.com/adguard-android/overview.html', + href: 'https://link.adtidy.org/forward.html?action=android&from=ui&app=home', }, text, ], @@ -63,7 +63,7 @@ const getDnsPrivacyList = () => [ components: [ { key: 0, - href: 'https://adguard.com/adguard-ios/overview.html', + href: 'https://link.adtidy.org/forward.html?action=ios&from=ui&app=home', }, text, ], diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js index e2186529..47e71f59 100644 --- a/client/src/components/ui/Icons.js +++ b/client/src/components/ui/Icons.js @@ -408,6 +408,10 @@ const Icons = () => ( + + + + ); diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 197cabba..935e3655 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -60,7 +60,7 @@ export const REPOSITORY = { export const CLIENT_ID_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#clientid'; export const MANUAL_UPDATE_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#manual-update'; export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse'; -export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html'; +export const PRIVACY_POLICY_LINK = 'https://link.adtidy.org/forward.html?action=privacy&from=ui&app=home'; export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams'; export const FILTERS_RELATIVE_LINK = '#filters'; @@ -81,7 +81,7 @@ export const STANDARD_DNS_PORT = 53; export const STANDARD_WEB_PORT = 80; export const STANDARD_HTTPS_PORT = 443; export const DNS_OVER_TLS_PORT = 853; -export const DNS_OVER_QUIC_PORT = 784; +export const DNS_OVER_QUIC_PORT = 853; export const MAX_PORT = 65535; export const EMPTY_DATE = '0001-01-01T00:00:00Z'; @@ -211,6 +211,10 @@ export const SERVICES = [ id: 'amazon', name: 'Amazon', }, + { + id: 'bilibili', + name: 'Bilibili', + }, { id: 'cloudflare', name: 'CloudFlare', @@ -588,7 +592,7 @@ export const FORM_NAME = { }; export const SMALL_SCREEN_SIZE = 767; -export const MEDIUM_SCREEN_SIZE = 1023; +export const MEDIUM_SCREEN_SIZE = 1024; export const SECONDS_IN_DAY = 60 * 60 * 24; diff --git a/client/src/helpers/filters/filters.json b/client/src/helpers/filters/filters.json index 565cd74d..63fe0995 100644 --- a/client/src/helpers/filters/filters.json +++ b/client/src/helpers/filters/filters.json @@ -81,8 +81,8 @@ "urlhaus-filter-online": { "name": "Online Malicious URL Blocklist", "categoryId": "security", - "homepage": "https://gitlab.com/curben/urlhaus-filter", - "source": "https://curben.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt" + "homepage": "https://gitlab.com/malware-filter/urlhaus-filter", + "source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt" }, "dandelion-sprouts-anti-malware-list": { "name": "Dandelion Sprout's Anti-Malware List", diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js index 862a2ca4..ed274416 100644 --- a/client/src/helpers/validators.js +++ b/client/src/helpers/validators.js @@ -339,3 +339,14 @@ export const validatePasswordLength = (value) => { } return undefined; }; + +/** + * @param value {string} + * @returns {Function} + */ +export const validateIpGateway = (value, allValues) => { + if (value === allValues.gatewayIp) { + return i18next.t('form_error_gateway_ip'); + } + return undefined; +}; diff --git a/client/src/i18n.js b/client/src/i18n.js index 8f0e46ea..7008459f 100644 --- a/client/src/i18n.js +++ b/client/src/i18n.js @@ -4,6 +4,7 @@ import langDetect from 'i18next-browser-languagedetector'; import { LANGUAGES, BASE_LOCALE } from './helpers/twosky'; +import ar from './__locales/ar.json'; import be from './__locales/be.json'; import bg from './__locales/bg.json'; import cs from './__locales/cs.json'; @@ -42,6 +43,7 @@ import zhTW from './__locales/zh-tw.json'; import { setHtmlLangAttr } from './helpers/helpers'; const resources = { + ar: { translation: ar }, be: { translation: be }, bg: { translation: bg }, cs: { translation: cs }, diff --git a/doc/adguard_home_darkmode.svg b/doc/adguard_home_darkmode.svg new file mode 100644 index 00000000..419134c4 --- /dev/null +++ b/doc/adguard_home_darkmode.svg @@ -0,0 +1,56 @@ + + + + + + + + + diff --git a/doc/adguard_home_lightmode.svg b/doc/adguard_home_lightmode.svg new file mode 100644 index 00000000..00965130 --- /dev/null +++ b/doc/adguard_home_lightmode.svg @@ -0,0 +1,55 @@ + + + + + + + + + diff --git a/go.mod b/go.mod index 3b64e7db..018afe80 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,39 @@ module github.com/AdguardTeam/AdGuardHome -go 1.17 +go 1.18 require ( - github.com/AdguardTeam/dnsproxy v0.42.2 - github.com/AdguardTeam/golibs v0.10.8 + github.com/AdguardTeam/dnsproxy v0.44.0 + github.com/AdguardTeam/golibs v0.10.9 github.com/AdguardTeam/urlfilter v0.16.0 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.2.3 github.com/digineo/go-ipset/v2 v2.2.1 + github.com/dimfeld/httptreemux/v5 v5.4.0 github.com/fsnotify/fsnotify v1.5.4 - github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 - github.com/google/go-cmp v0.5.7 + github.com/go-ping/ping v1.1.0 + github.com/google/go-cmp v0.5.8 github.com/google/gopacket v1.1.19 github.com/google/renameio v1.0.1 - github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 + github.com/google/uuid v1.3.0 + github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f github.com/kardianos/service v1.2.1 - github.com/lucas-clemente/quic-go v0.26.0 + github.com/lucas-clemente/quic-go v0.28.1 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/netlink v1.6.0 // TODO(a.garipov): This package is deprecated; find a new one or use - // our own code for that. - github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b - github.com/miekg/dns v1.1.48 - github.com/satori/go.uuid v1.2.0 - github.com/stretchr/testify v1.7.0 + // our own code for that. Perhaps, use gopacket. + github.com/mdlayher/raw v0.1.0 + github.com/miekg/dns v1.1.50 + github.com/stretchr/testify v1.7.1 github.com/ti-mo/netfilter v0.4.0 go.etcd.io/bbolt v1.3.6 - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 - golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 + golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e + golang.org/x/net v0.0.0-20220812174116-3211cb980234 + golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 ) @@ -44,11 +46,12 @@ require ( github.com/cheekybits/genny v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/josharian/native v1.0.0 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect - github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect + github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect + github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect + github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect + github.com/mdlayher/packet v1.0.0 // indirect github.com/mdlayher/socket v0.2.3 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect @@ -57,14 +60,9 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.1.1 // indirect github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + golang.org/x/tools v0.1.12 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) - -// TODO(a.garipov): Return to the main repo once miekg/dns#1359 is merged. -replace github.com/miekg/dns => github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8 diff --git a/go.sum b/go.sum index 06ae9b96..240544dc 100644 --- a/go.sum +++ b/go.sum @@ -7,13 +7,13 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.42.2 h1:aBhbuvqg/rZN8Rab5ILSfPFJDkiTviWXXcceJgajnNs= -github.com/AdguardTeam/dnsproxy v0.42.2/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0= +github.com/AdguardTeam/dnsproxy v0.44.0 h1:JzIxEXF4OyJq4wZVHeZkM1af4VfuwcgrUzjgdBGljsE= +github.com/AdguardTeam/dnsproxy v0.44.0/go.mod h1:HsxYYW/bC8uo+4eX9pRW21hFD6gWZdrvcfBb1R6/AzU= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.10.8 h1:diU9gP9qG1qeLbAkzIwfUerpHSqzR6zaBgzvRMR/m6Q= -github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= +github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0= +github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg= github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= @@ -28,15 +28,12 @@ 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/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8 h1:Hp2waLwK989ui3bDkFpedlIHfyWdZ77gynvd+GPEqXY= -github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/ameshkov/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI= github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs= github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM= github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -52,13 +49,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g= github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU= +github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs= +github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -66,8 +64,8 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= -github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= +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-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -97,8 +95,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -118,11 +117,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I= -github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -141,23 +139,20 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= -github.com/lucas-clemente/quic-go v0.26.0 h1:ALBQXr9UJ8A1LyzvceX4jd9QFsHvlI0RR6BkV16o00A= -github.com/lucas-clemente/quic-go v0.26.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= +github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU= +github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= -github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc= -github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= -github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= -github.com/marten-seemann/qtls-go1-18 v0.1.0/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= -github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y= -github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ= +github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= +github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= +github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= +github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE= @@ -170,16 +165,21 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= +github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b h1:MHcTarUMC4sFA7eiyR8IEJ6j2PgmgXR+B9X2IIMjh7A= -github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +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.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -213,8 +213,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= @@ -251,8 +249,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= github.com/ti-mo/netfilter v0.4.0 h1:rTN1nBYULDmMfDeBHZpKuNKX/bWEXQUhe02a/10orzg= @@ -268,7 +267,6 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -281,11 +279,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -293,9 +291,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0= +golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -311,6 +308,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -327,12 +325,10 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -344,8 +340,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -360,6 +357,7 @@ golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -376,8 +374,8 @@ golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -387,14 +385,13 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -414,19 +411,17 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 h1:pODAJF0uBqx6zFa1MYaiTobVo5FzCbnTVUXeO8o71fE= -golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -471,8 +466,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/aghalg/aghalg.go b/internal/aghalg/aghalg.go index 3a9b07db..f0b71a09 100644 --- a/internal/aghalg/aghalg.go +++ b/internal/aghalg/aghalg.go @@ -1,32 +1,45 @@ // Package aghalg contains common generic algorithms and data structures. // -// TODO(a.garipov): Update to use type parameters in Go 1.18. +// TODO(a.garipov): Move parts of this into golibs. package aghalg import ( "fmt" - "sort" + + "golang.org/x/exp/constraints" + "golang.org/x/exp/slices" ) -// comparable is an alias for interface{}. Values passed as arguments of this -// type alias must be comparable. -// -// TODO(a.garipov): Remove in Go 1.18. -type comparable = interface{} +// Coalesce returns the first non-zero value. It is named after the function +// COALESCE in SQL. If values or all its elements are empty, it returns a zero +// value. +func Coalesce[T comparable](values ...T) (res T) { + var zero T + for _, v := range values { + if v != zero { + return v + } + } + + return zero +} // UniqChecker allows validating uniqueness of comparable items. -type UniqChecker map[comparable]int64 +// +// TODO(a.garipov): The Ordered constraint is only really necessary in Validate. +// Consider ways of making this constraint comparable instead. +type UniqChecker[T constraints.Ordered] map[T]int64 // Add adds a value to the validator. v must not be nil. -func (uc UniqChecker) Add(elems ...comparable) { +func (uc UniqChecker[T]) Add(elems ...T) { for _, e := range elems { uc[e]++ } } // Merge returns a checker containing data from both uc and other. -func (uc UniqChecker) Merge(other UniqChecker) (merged UniqChecker) { - merged = make(UniqChecker, len(uc)+len(other)) +func (uc UniqChecker[T]) Merge(other UniqChecker[T]) (merged UniqChecker[T]) { + merged = make(UniqChecker[T], len(uc)+len(other)) for elem, num := range uc { merged[elem] += num } @@ -39,10 +52,8 @@ func (uc UniqChecker) Merge(other UniqChecker) (merged UniqChecker) { } // Validate returns an error enumerating all elements that aren't unique. -// isBefore is an optional sorting function to make the error message -// deterministic. -func (uc UniqChecker) Validate(isBefore func(a, b comparable) (less bool)) (err error) { - var dup []comparable +func (uc UniqChecker[T]) Validate() (err error) { + var dup []T for elem, num := range uc { if num > 1 { dup = append(dup, elem) @@ -53,23 +64,7 @@ func (uc UniqChecker) Validate(isBefore func(a, b comparable) (less bool)) (err return nil } - if isBefore != nil { - sort.Slice(dup, func(i, j int) (less bool) { - return isBefore(dup[i], dup[j]) - }) - } + slices.Sort(dup) return fmt.Errorf("duplicated values: %v", dup) } - -// IntIsBefore is a helper sort function for UniqChecker.Validate. -// a and b must be of type int. -func IntIsBefore(a, b comparable) (less bool) { - return a.(int) < b.(int) -} - -// StringIsBefore is a helper sort function for UniqChecker.Validate. -// a and b must be of type string. -func StringIsBefore(a, b comparable) (less bool) { - return a.(string) < b.(string) -} diff --git a/internal/aghalg/nullbool.go b/internal/aghalg/nullbool.go new file mode 100644 index 00000000..01a8c974 --- /dev/null +++ b/internal/aghalg/nullbool.go @@ -0,0 +1,67 @@ +package aghalg + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// NullBool is a nullable boolean. Use these in JSON requests and responses +// instead of pointers to bool. +type NullBool uint8 + +// NullBool values +const ( + NBNull NullBool = iota + NBTrue + NBFalse +) + +// String implements the fmt.Stringer interface for NullBool. +func (nb NullBool) String() (s string) { + switch nb { + case NBNull: + return "null" + case NBTrue: + return "true" + case NBFalse: + return "false" + } + + return fmt.Sprintf("!invalid NullBool %d", uint8(nb)) +} + +// BoolToNullBool converts a bool into a NullBool. +func BoolToNullBool(cond bool) (nb NullBool) { + if cond { + return NBTrue + } + + return NBFalse +} + +// type check +var _ json.Marshaler = NBNull + +// MarshalJSON implements the json.Marshaler interface for NullBool. +func (nb NullBool) MarshalJSON() (b []byte, err error) { + return []byte(nb.String()), nil +} + +// type check +var _ json.Unmarshaler = (*NullBool)(nil) + +// UnmarshalJSON implements the json.Unmarshaler interface for *NullBool. +func (nb *NullBool) UnmarshalJSON(b []byte) (err error) { + if len(b) == 0 || bytes.Equal(b, []byte("null")) { + *nb = NBNull + } else if bytes.Equal(b, []byte("true")) { + *nb = NBTrue + } else if bytes.Equal(b, []byte("false")) { + *nb = NBFalse + } else { + return fmt.Errorf("unmarshalling json data into aghalg.NullBool: bad value %q", b) + } + + return nil +} diff --git a/internal/aghalg/nullbool_test.go b/internal/aghalg/nullbool_test.go new file mode 100644 index 00000000..8096f1069 --- /dev/null +++ b/internal/aghalg/nullbool_test.go @@ -0,0 +1,113 @@ +package aghalg_test + +import ( + "encoding/json" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNullBool_MarshalJSON(t *testing.T) { + testCases := []struct { + name string + wantErrMsg string + want []byte + in aghalg.NullBool + }{{ + name: "null", + wantErrMsg: "", + want: []byte("null"), + in: aghalg.NBNull, + }, { + name: "true", + wantErrMsg: "", + want: []byte("true"), + in: aghalg.NBTrue, + }, { + name: "false", + wantErrMsg: "", + want: []byte("false"), + in: aghalg.NBFalse, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.in.MarshalJSON() + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, got) + }) + } + + t.Run("json", func(t *testing.T) { + in := &struct { + A aghalg.NullBool + }{ + A: aghalg.NBTrue, + } + + got, err := json.Marshal(in) + require.NoError(t, err) + + assert.Equal(t, []byte(`{"A":true}`), got) + }) +} + +func TestNullBool_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + wantErrMsg string + data []byte + want aghalg.NullBool + }{{ + name: "empty", + wantErrMsg: "", + data: []byte{}, + want: aghalg.NBNull, + }, { + name: "null", + wantErrMsg: "", + data: []byte("null"), + want: aghalg.NBNull, + }, { + name: "true", + wantErrMsg: "", + data: []byte("true"), + want: aghalg.NBTrue, + }, { + name: "false", + wantErrMsg: "", + data: []byte("false"), + want: aghalg.NBFalse, + }, { + name: "invalid", + wantErrMsg: `unmarshalling json data into aghalg.NullBool: bad value "invalid"`, + data: []byte("invalid"), + want: aghalg.NBNull, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got aghalg.NullBool + err := got.UnmarshalJSON(tc.data) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.want, got) + }) + } + + t.Run("json", func(t *testing.T) { + want := aghalg.NBTrue + var got struct { + A aghalg.NullBool + } + + err := json.Unmarshal([]byte(`{"A":true}`), &got) + require.NoError(t, err) + + assert.Equal(t, want, got.A) + }) +} diff --git a/internal/aghhttp/aghhttp.go b/internal/aghhttp/aghhttp.go index 666342ea..57a1c868 100644 --- a/internal/aghhttp/aghhttp.go +++ b/internal/aghhttp/aghhttp.go @@ -9,6 +9,12 @@ import ( "github.com/AdguardTeam/golibs/log" ) +// RegisterFunc is the function that sets the handler to handle the URL for the +// method. +// +// TODO(e.burkov, a.garipov): Get rid of it. +type RegisterFunc func(method, url string, handler http.HandlerFunc) + // OK responds with word OK. func OK(w http.ResponseWriter) { if _, err := io.WriteString(w, "OK\n"); err != nil { @@ -17,7 +23,7 @@ func OK(w http.ResponseWriter) { } // Error writes formatted message to w and also logs it. -func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) { +func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) { text := fmt.Sprintf(format, args...) log.Error("%s %s: %s", r.Method, r.URL, text) http.Error(w, text, code) diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go index 65c9d3c4..ee345905 100644 --- a/internal/aghnet/hostscontainer.go +++ b/internal/aghnet/hostscontainer.go @@ -198,7 +198,7 @@ func (hc *HostsContainer) Close() (err error) { } // Upd returns the channel into which the updates are sent. The receivable -// map's values are guaranteed to be of type of *stringutil.Set. +// map's values are guaranteed to be of type of *HostsRecord. func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) { return hc.updates } @@ -290,7 +290,7 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err continue } - hp.addPairs(ip, hosts) + hp.addRecord(ip, hosts) } return nil, true, s.Err() @@ -335,39 +335,66 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) { return ip, hosts } -// addPair puts the pair of ip and host to the rules builder if needed. For -// each ip the first member of hosts will become the main one. -func (hp *hostsParser) addPairs(ip net.IP, hosts []string) { +// HostsRecord represents a single hosts file record. +type HostsRecord struct { + Aliases *stringutil.Set + Canonical string +} + +// Equal returns true if all fields of rec are equal to field in other or they +// both are nil. +func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) { + if rec == nil { + return other == nil + } + + return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases) +} + +// addRecord puts the record for the IP address to the rules builder if needed. +// The first host is considered to be the canonical name for the IP address. +// hosts must have at least one name. +func (hp *hostsParser) addRecord(ip net.IP, hosts []string) { + line := strings.Join(append([]string{ip.String()}, hosts...), " ") + + var rec *HostsRecord v, ok := hp.table.Get(ip) if !ok { - // This ip is added at the first time. - v = stringutil.NewSet() - hp.table.Set(ip, v) + rec = &HostsRecord{ + Aliases: stringutil.NewSet(), + } + + rec.Canonical, hosts = hosts[0], hosts[1:] + hp.addRules(ip, rec.Canonical, line) + hp.table.Set(ip, rec) + } else { + rec, ok = v.(*HostsRecord) + if !ok { + log.Error("%s: adding pairs: unexpected type %T", hostsContainerPref, v) + + return + } } - var set *stringutil.Set - set, ok = v.(*stringutil.Set) - if !ok { - log.Debug("%s: adding pairs: unexpected value type %T", hostsContainerPref, v) - - return - } - - processed := strings.Join(append([]string{ip.String()}, hosts...), " ") - for _, h := range hosts { - if set.Has(h) { + for _, host := range hosts { + if rec.Canonical == host || rec.Aliases.Has(host) { continue } - set.Add(h) + rec.Aliases.Add(host) - rule, rulePtr := hp.writeRules(h, ip) - hp.translations[rule], hp.translations[rulePtr] = processed, processed - - log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, h) + hp.addRules(ip, host, line) } } +// addRules adds rules and rule translations for the line. +func (hp *hostsParser) addRules(ip net.IP, host, line string) { + rule, rulePtr := hp.writeRules(host, ip) + hp.translations[rule], hp.translations[rulePtr] = line, line + + log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, host) +} + // writeRules writes the actual rule for the qtype and the PTR for the host-ip // pair into internal builders. func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) { @@ -417,6 +444,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) } // equalSet returns true if the internal hosts table just parsed equals target. +// target's values must be of type *HostsRecord. func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) { if target == nil { // hp.table shouldn't appear nil since it's initialized on each refresh. @@ -427,22 +455,35 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) { return false } - hp.table.Range(func(ip net.IP, b interface{}) (cont bool) { - // ok is set to true if the target doesn't contain ip or if the - // appropriate hosts set isn't equal to the checked one. - if a, hasIP := target.Get(ip); !hasIP { - ok = true - } else if hosts, aok := a.(*stringutil.Set); aok { - ok = !hosts.Equal(b.(*stringutil.Set)) + hp.table.Range(func(ip net.IP, recVal any) (cont bool) { + var targetVal any + targetVal, ok = target.Get(ip) + if !ok { + return false } - // Continue only if maps has no discrepancies. - return !ok + var rec *HostsRecord + rec, ok = recVal.(*HostsRecord) + if !ok { + log.Error("%s: comparing: unexpected type %T", hostsContainerPref, recVal) + + return false + } + + var targetRec *HostsRecord + targetRec, ok = targetVal.(*HostsRecord) + if !ok { + log.Error("%s: comparing: target: unexpected type %T", hostsContainerPref, targetVal) + + return false + } + + ok = rec.Equal(targetRec) + + return ok }) - // Return true if every value from the IP map has no discrepancies with the - // appropriate one from the target. - return !ok + return ok } // sendUpd tries to send the parsed data to the ch. diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go index 42202f43..1f75a3c9 100644 --- a/internal/aghnet/hostscontainer_test.go +++ b/internal/aghnet/hostscontainer_test.go @@ -12,6 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/urlfilter" @@ -159,31 +160,47 @@ func TestHostsContainer_refresh(t *testing.T) { require.NoError(t, err) testutil.CleanupAndRequireSuccess(t, hc.Close) - checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) { - upd, ok := <-hc.Upd() - require.True(t, ok) - require.NotNil(t, upd) + checkRefresh := func(t *testing.T, want *HostsRecord) { + t.Helper() + + var ok bool + var upd *netutil.IPMap + select { + case upd, ok = <-hc.Upd(): + require.True(t, ok) + require.NotNil(t, upd) + case <-time.After(1 * time.Second): + t.Fatal("did not receive after 1s") + } assert.Equal(t, 1, upd.Len()) v, ok := upd.Get(ip) require.True(t, ok) - var set *stringutil.Set - set, ok = v.(*stringutil.Set) - require.True(t, ok) + require.IsType(t, (*HostsRecord)(nil), v) - assert.True(t, set.Equal(wantHosts)) + rec, _ := v.(*HostsRecord) + require.NotNil(t, rec) + + assert.Truef(t, rec.Equal(want), "%+v != %+v", rec, want) } t.Run("initial_refresh", func(t *testing.T) { - checkRefresh(t, stringutil.NewSet("hostname")) + checkRefresh(t, &HostsRecord{ + Aliases: stringutil.NewSet(), + Canonical: "hostname", + }) }) t.Run("second_refresh", func(t *testing.T) { testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)} eventsCh <- event{} - checkRefresh(t, stringutil.NewSet("hostname", "alias")) + + checkRefresh(t, &HostsRecord{ + Aliases: stringutil.NewSet("alias"), + Canonical: "hostname", + }) }) t.Run("double_refresh", func(t *testing.T) { @@ -363,10 +380,15 @@ func TestHostsContainer(t *testing.T) { require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) testCases := []struct { - want []*rules.DNSRewrite - name string req *urlfilter.DNSRequest + name string + want []*rules.DNSRewrite }{{ + req: &urlfilter.DNSRequest{ + Hostname: "simplehost", + DNSType: dns.TypeA, + }, + name: "simple", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, Value: net.IPv4(1, 0, 0, 1), @@ -376,27 +398,12 @@ func TestHostsContainer(t *testing.T) { Value: net.ParseIP("::1"), RRType: dns.TypeAAAA, }}, - name: "simple", - req: &urlfilter.DNSRequest{ - Hostname: "simplehost", - DNSType: dns.TypeA, - }, }, { - want: []*rules.DNSRewrite{{ - RCode: dns.RcodeSuccess, - Value: net.IPv4(1, 0, 0, 0), - RRType: dns.TypeA, - }, { - RCode: dns.RcodeSuccess, - Value: net.ParseIP("::"), - RRType: dns.TypeAAAA, - }}, - name: "hello_alias", req: &urlfilter.DNSRequest{ Hostname: "hello.world", DNSType: dns.TypeA, }, - }, { + name: "hello_alias", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, Value: net.IPv4(1, 0, 0, 0), @@ -406,26 +413,41 @@ func TestHostsContainer(t *testing.T) { Value: net.ParseIP("::"), RRType: dns.TypeAAAA, }}, - name: "other_line_alias", + }, { req: &urlfilter.DNSRequest{ Hostname: "hello.world.again", DNSType: dns.TypeA, }, + name: "other_line_alias", + want: []*rules.DNSRewrite{{ + RCode: dns.RcodeSuccess, + Value: net.IPv4(1, 0, 0, 0), + RRType: dns.TypeA, + }, { + RCode: dns.RcodeSuccess, + Value: net.ParseIP("::"), + RRType: dns.TypeAAAA, + }}, }, { - want: []*rules.DNSRewrite{}, - name: "hello_subdomain", req: &urlfilter.DNSRequest{ Hostname: "say.hello", DNSType: dns.TypeA, }, - }, { + name: "hello_subdomain", want: []*rules.DNSRewrite{}, - name: "hello_alias_subdomain", + }, { req: &urlfilter.DNSRequest{ Hostname: "say.hello.world", DNSType: dns.TypeA, }, + name: "hello_alias_subdomain", + want: []*rules.DNSRewrite{}, }, { + req: &urlfilter.DNSRequest{ + Hostname: "for.testing", + DNSType: dns.TypeA, + }, + name: "lots_of_aliases", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -435,37 +457,37 @@ func TestHostsContainer(t *testing.T) { RRType: dns.TypeAAAA, Value: net.ParseIP("::2"), }}, - name: "lots_of_aliases", - req: &urlfilter.DNSRequest{ - Hostname: "for.testing", - DNSType: dns.TypeA, - }, }, { + req: &urlfilter.DNSRequest{ + Hostname: "1.0.0.1.in-addr.arpa", + DNSType: dns.TypePTR, + }, + name: "reverse", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, RRType: dns.TypePTR, Value: "simplehost.", }}, - name: "reverse", - req: &urlfilter.DNSRequest{ - Hostname: "1.0.0.1.in-addr.arpa", - DNSType: dns.TypePTR, - }, }, { - want: []*rules.DNSRewrite{}, - name: "non-existing", req: &urlfilter.DNSRequest{ - Hostname: "nonexisting", + Hostname: "nonexistent.example", DNSType: dns.TypeA, }, + name: "non-existing", + want: []*rules.DNSRewrite{}, }, { - want: nil, - name: "bad_type", req: &urlfilter.DNSRequest{ Hostname: "1.0.0.1.in-addr.arpa", DNSType: dns.TypeSRV, }, + name: "bad_type", + want: nil, }, { + req: &urlfilter.DNSRequest{ + Hostname: "domain", + DNSType: dns.TypeA, + }, + name: "issue_4216_4_6", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -475,12 +497,12 @@ func TestHostsContainer(t *testing.T) { RRType: dns.TypeAAAA, Value: net.ParseIP("::42"), }}, - name: "issue_4216_4_6", + }, { req: &urlfilter.DNSRequest{ - Hostname: "domain", + Hostname: "domain4", DNSType: dns.TypeA, }, - }, { + name: "issue_4216_4", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, RRType: dns.TypeA, @@ -490,12 +512,12 @@ func TestHostsContainer(t *testing.T) { RRType: dns.TypeA, Value: net.IPv4(1, 3, 5, 7), }}, - name: "issue_4216_4", - req: &urlfilter.DNSRequest{ - Hostname: "domain4", - DNSType: dns.TypeA, - }, }, { + req: &urlfilter.DNSRequest{ + Hostname: "domain6", + DNSType: dns.TypeAAAA, + }, + name: "issue_4216_6", want: []*rules.DNSRewrite{{ RCode: dns.RcodeSuccess, RRType: dns.TypeAAAA, @@ -505,11 +527,6 @@ func TestHostsContainer(t *testing.T) { RRType: dns.TypeAAAA, Value: net.ParseIP("::31"), }}, - name: "issue_4216_6", - req: &urlfilter.DNSRequest{ - Hostname: "domain6", - DNSType: dns.TypeAAAA, - }, }} stubWatcher := aghtest.FSWatcher{ diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go index 268380bd..2de9c630 100644 --- a/internal/aghnet/net.go +++ b/internal/aghnet/net.go @@ -154,10 +154,13 @@ func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) { return netIfaces, nil } -// GetInterfaceByIP returns the name of interface containing provided ip. +// InterfaceByIP returns the name of the interface bound to ip. // -// TODO(e.burkov): See TODO on GetValidInterfacesForWeb. -func GetInterfaceByIP(ip net.IP) string { +// TODO(a.garipov, e.burkov): This function is technically incorrect, since one +// IP address can be shared by multiple interfaces in some configurations. +// +// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb. +func InterfaceByIP(ip net.IP) (ifaceName string) { ifaces, err := GetValidNetInterfacesForWeb() if err != nil { return "" @@ -177,7 +180,7 @@ func GetInterfaceByIP(ip net.IP) string { // GetSubnet returns pointer to net.IPNet for the specified interface or nil if // the search fails. // -// TODO(e.burkov): See TODO on GetValidInterfacesForWeb. +// TODO(e.burkov): See TODO on GetValidNetInterfacesForWeb. func GetSubnet(ifaceName string) *net.IPNet { netIfaces, err := GetValidNetInterfacesForWeb() if err != nil { diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go index 148abe1f..4be8835c 100644 --- a/internal/aghnet/net_linux.go +++ b/internal/aghnet/net_linux.go @@ -13,6 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/stringutil" "github.com/google/renameio/maybe" "golang.org/x/sys/unix" @@ -22,17 +23,27 @@ import ( const dhcpcdConf = "etc/dhcpcd.conf" func canBindPrivilegedPorts() (can bool, err error) { - cnbs, err := unix.PrctlRetInt( + res, err := unix.PrctlRetInt( unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0, ) + if err != nil { + if errors.Is(err, unix.EINVAL) { + // Older versions of Linux kernel do not support this. Print a + // warning and check admin rights. + log.Info("warning: cannot check capability cap_net_bind_service: %s", err) + } else { + return false, err + } + } + // Don't check the error because it's always nil on Linux. adm, _ := aghos.HaveAdminRights() - return cnbs == 1 || adm, err + return res == 1 || adm, nil } // dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go index 40d395ba..d4ee59ee 100644 --- a/internal/aghnet/net_test.go +++ b/internal/aghnet/net_test.go @@ -132,7 +132,7 @@ func TestGatewayIP(t *testing.T) { } } -func TestGetInterfaceByIP(t *testing.T) { +func TestInterfaceByIP(t *testing.T) { ifaces, err := GetValidNetInterfacesForWeb() require.NoError(t, err) require.NotEmpty(t, ifaces) @@ -142,7 +142,7 @@ func TestGetInterfaceByIP(t *testing.T) { require.NotEmpty(t, iface.Addresses) for _, ip := range iface.Addresses { - ifaceName := GetInterfaceByIP(ip) + ifaceName := InterfaceByIP(ip) require.Equal(t, iface.Name, ifaceName) } }) diff --git a/internal/aghnet/systemresolvers.go b/internal/aghnet/systemresolvers.go index 13fbeb32..5ca8e9be 100644 --- a/internal/aghnet/systemresolvers.go +++ b/internal/aghnet/systemresolvers.go @@ -19,7 +19,7 @@ type SystemResolvers interface { } // NewSystemResolvers returns a SystemResolvers with the cache refresh rate -// defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0. If +// defined by refreshIvl. It disables auto-refreshing if refreshIvl is 0. If // nil is passed for hostGenFunc, the default generator will be used. func NewSystemResolvers( hostGenFunc HostGenFunc, diff --git a/internal/aghos/aghos_test.go b/internal/aghos/aghos_test.go index e68c26b7..684f646e 100644 --- a/internal/aghos/aghos_test.go +++ b/internal/aghos/aghos_test.go @@ -1,4 +1,4 @@ -package aghos +package aghos_test import ( "testing" diff --git a/internal/aghos/filewalker_internal_test.go b/internal/aghos/filewalker_internal_test.go new file mode 100644 index 00000000..bb162812 --- /dev/null +++ b/internal/aghos/filewalker_internal_test.go @@ -0,0 +1,57 @@ +package aghos + +import ( + "io/fs" + "path" + "testing" + "testing/fstest" + + "github.com/AdguardTeam/golibs/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// errFS is an fs.FS implementation, method Open of which always returns +// errFSOpen. +type errFS struct{} + +// errFSOpen is returned from errGlobFS.Open. +const errFSOpen errors.Error = "test open error" + +// Open implements the fs.FS interface for *errGlobFS. fsys is always nil and +// err is always errFSOpen. +func (efs *errFS) Open(name string) (fsys fs.File, err error) { + return nil, errFSOpen +} + +func TestWalkerFunc_CheckFile(t *testing.T) { + emptyFS := fstest.MapFS{} + + t.Run("non-existing", func(t *testing.T) { + _, ok, err := checkFile(emptyFS, nil, "lol") + require.NoError(t, err) + + assert.True(t, ok) + }) + + t.Run("invalid_argument", func(t *testing.T) { + _, ok, err := checkFile(&errFS{}, nil, "") + require.ErrorIs(t, err, errFSOpen) + + assert.False(t, ok) + }) + + t.Run("ignore_dirs", func(t *testing.T) { + const dirName = "dir" + + testFS := fstest.MapFS{ + path.Join(dirName, "file"): &fstest.MapFile{Data: []byte{}}, + } + + patterns, ok, err := checkFile(testFS, nil, dirName) + require.NoError(t, err) + + assert.Empty(t, patterns) + assert.True(t, ok) + }) +} diff --git a/internal/aghos/filewalker_test.go b/internal/aghos/filewalker_test.go index 97d1a845..94443831 100644 --- a/internal/aghos/filewalker_test.go +++ b/internal/aghos/filewalker_test.go @@ -1,13 +1,13 @@ -package aghos +package aghos_test import ( "bufio" "io" - "io/fs" "path" "testing" "testing/fstest" + "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,7 +16,7 @@ import ( func TestFileWalker_Walk(t *testing.T) { const attribute = `000` - makeFileWalker := func(_ string) (fw FileWalker) { + makeFileWalker := func(_ string) (fw aghos.FileWalker) { return func(r io.Reader) (patterns []string, cont bool, err error) { s := bufio.NewScanner(r) for s.Scan() { @@ -113,7 +113,7 @@ func TestFileWalker_Walk(t *testing.T) { f := fstest.MapFS{ filename: &fstest.MapFile{Data: []byte("[]")}, } - ok, err := FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) { + ok, err := aghos.FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) { s := bufio.NewScanner(r) for s.Scan() { patterns = append(patterns, s.Text()) @@ -134,7 +134,7 @@ func TestFileWalker_Walk(t *testing.T) { "mockfile.txt": &fstest.MapFile{Data: []byte(`mockdata`)}, } - ok, err := FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) { + ok, err := aghos.FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) { return nil, true, rerr }).Walk(f, "*") require.ErrorIs(t, err, rerr) @@ -142,45 +142,3 @@ func TestFileWalker_Walk(t *testing.T) { assert.False(t, ok) }) } - -type errFS struct { - fs.GlobFS -} - -const errErrFSOpen errors.Error = "this error is always returned" - -func (efs *errFS) Open(name string) (fs.File, error) { - return nil, errErrFSOpen -} - -func TestWalkerFunc_CheckFile(t *testing.T) { - emptyFS := fstest.MapFS{} - - t.Run("non-existing", func(t *testing.T) { - _, ok, err := checkFile(emptyFS, nil, "lol") - require.NoError(t, err) - - assert.True(t, ok) - }) - - t.Run("invalid_argument", func(t *testing.T) { - _, ok, err := checkFile(&errFS{}, nil, "") - require.ErrorIs(t, err, errErrFSOpen) - - assert.False(t, ok) - }) - - t.Run("ignore_dirs", func(t *testing.T) { - const dirName = "dir" - - testFS := fstest.MapFS{ - path.Join(dirName, "file"): &fstest.MapFile{Data: []byte{}}, - } - - patterns, ok, err := checkFile(testFS, nil, dirName) - require.NoError(t, err) - - assert.Empty(t, patterns) - assert.True(t, ok) - }) -} diff --git a/internal/aghtest/exchanger.go b/internal/aghtest/exchanger.go deleted file mode 100644 index 2c617814..00000000 --- a/internal/aghtest/exchanger.go +++ /dev/null @@ -1,20 +0,0 @@ -package aghtest - -import ( - "github.com/AdguardTeam/dnsproxy/upstream" - "github.com/miekg/dns" -) - -// Exchanger is a mock aghnet.Exchanger implementation for tests. -type Exchanger struct { - Ups upstream.Upstream -} - -// Exchange implements aghnet.Exchanger interface for *Exchanger. -func (e *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) { - if e.Ups == nil { - e.Ups = &TestErrUpstream{} - } - - return e.Ups.Exchange(req) -} diff --git a/internal/aghtest/fswatcher.go b/internal/aghtest/fswatcher.go deleted file mode 100644 index 0df4470d..00000000 --- a/internal/aghtest/fswatcher.go +++ /dev/null @@ -1,23 +0,0 @@ -package aghtest - -// FSWatcher is a mock aghos.FSWatcher implementation to use in tests. -type FSWatcher struct { - OnEvents func() (e <-chan struct{}) - OnAdd func(name string) (err error) - OnClose func() (err error) -} - -// Events implements the aghos.FSWatcher interface for *FSWatcher. -func (w *FSWatcher) Events() (e <-chan struct{}) { - return w.OnEvents() -} - -// Add implements the aghos.FSWatcher interface for *FSWatcher. -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() -} diff --git a/internal/aghtest/interface.go b/internal/aghtest/interface.go new file mode 100644 index 00000000..2de9d372 --- /dev/null +++ b/internal/aghtest/interface.go @@ -0,0 +1,135 @@ +package aghtest + +import ( + "io/fs" + "net" + + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/miekg/dns" +) + +// Interface Mocks +// +// Keep entities in this file in alphabetic order. + +// Standard Library + +// type check +var _ fs.FS = &FS{} + +// FS is a mock [fs.FS] implementation for tests. +type FS struct { + OnOpen func(name string) (fs.File, error) +} + +// Open implements the [fs.FS] interface for *FS. +func (fsys *FS) Open(name string) (fs.File, error) { + return fsys.OnOpen(name) +} + +// type check +var _ fs.GlobFS = &GlobFS{} + +// GlobFS is a mock [fs.GlobFS] implementation for tests. +type GlobFS struct { + // FS is embedded here to avoid implementing all it's methods. + FS + OnGlob func(pattern string) ([]string, error) +} + +// Glob implements the [fs.GlobFS] interface for *GlobFS. +func (fsys *GlobFS) Glob(pattern string) ([]string, error) { + return fsys.OnGlob(pattern) +} + +// type check +var _ fs.StatFS = &StatFS{} + +// StatFS is a mock [fs.StatFS] implementation for tests. +type StatFS struct { + // FS is embedded here to avoid implementing all it's methods. + FS + OnStat func(name string) (fs.FileInfo, error) +} + +// Stat implements the [fs.StatFS] interface for *StatFS. +func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) { + return fsys.OnStat(name) +} + +// type check +var _ net.Listener = (*Listener)(nil) + +// Listener is a mock [net.Listener] implementation for tests. +type Listener struct { + OnAccept func() (conn net.Conn, err error) + OnAddr func() (addr net.Addr) + OnClose func() (err error) +} + +// Accept implements the [net.Listener] interface for *Listener. +func (l *Listener) Accept() (conn net.Conn, err error) { + return l.OnAccept() +} + +// Addr implements the [net.Listener] interface for *Listener. +func (l *Listener) Addr() (addr net.Addr) { + return l.OnAddr() +} + +// Close implements the [net.Listener] interface for *Listener. +func (l *Listener) Close() (err error) { + return l.OnClose() +} + +// Module dnsproxy + +// type check +var _ upstream.Upstream = (*UpstreamMock)(nil) + +// UpstreamMock is a mock [upstream.Upstream] implementation for tests. +// +// TODO(a.garipov): Replace with all uses of Upstream with UpstreamMock and +// rename it to just Upstream. +type UpstreamMock struct { + OnAddress func() (addr string) + OnExchange func(req *dns.Msg) (resp *dns.Msg, err error) +} + +// Address implements the [upstream.Upstream] interface for *UpstreamMock. +func (u *UpstreamMock) Address() (addr string) { + return u.OnAddress() +} + +// Exchange implements the [upstream.Upstream] interface for *UpstreamMock. +func (u *UpstreamMock) Exchange(req *dns.Msg) (resp *dns.Msg, err error) { + return u.OnExchange(req) +} + +// Module AdGuardHome + +// type check +var _ aghos.FSWatcher = (*FSWatcher)(nil) + +// FSWatcher is a mock [aghos.FSWatcher] implementation for tests. +type FSWatcher struct { + OnEvents func() (e <-chan struct{}) + OnAdd func(name string) (err error) + OnClose func() (err error) +} + +// Events implements the [aghos.FSWatcher] interface for *FSWatcher. +func (w *FSWatcher) Events() (e <-chan struct{}) { + return w.OnEvents() +} + +// Add implements the [aghos.FSWatcher] interface for *FSWatcher. +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() +} diff --git a/internal/aghtest/interface_test.go b/internal/aghtest/interface_test.go new file mode 100644 index 00000000..5a465c2c --- /dev/null +++ b/internal/aghtest/interface_test.go @@ -0,0 +1,9 @@ +package aghtest_test + +import ( + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" +) + +// type check +var _ aghos.FSWatcher = (*aghtest.FSWatcher)(nil) diff --git a/internal/aghtest/testfs.go b/internal/aghtest/testfs.go deleted file mode 100644 index 88203fec..00000000 --- a/internal/aghtest/testfs.go +++ /dev/null @@ -1,46 +0,0 @@ -package aghtest - -import "io/fs" - -// type check -var _ fs.FS = &FS{} - -// FS is a mock fs.FS implementation to use in tests. -type FS struct { - OnOpen func(name string) (fs.File, error) -} - -// Open implements the fs.FS interface for *FS. -func (fsys *FS) Open(name string) (fs.File, error) { - return fsys.OnOpen(name) -} - -// type check -var _ fs.StatFS = &StatFS{} - -// StatFS is a mock fs.StatFS implementation to use in tests. -type StatFS struct { - // FS is embedded here to avoid implementing all it's methods. - FS - OnStat func(name string) (fs.FileInfo, error) -} - -// Stat implements the fs.StatFS interface for *StatFS. -func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) { - return fsys.OnStat(name) -} - -// type check -var _ fs.GlobFS = &GlobFS{} - -// GlobFS is a mock fs.GlobFS implementation to use in tests. -type GlobFS struct { - // FS is embedded here to avoid implementing all it's methods. - FS - OnGlob func(pattern string) ([]string, error) -} - -// Glob implements the fs.GlobFS interface for *GlobFS. -func (fsys *GlobFS) Glob(pattern string) ([]string, error) { - return fsys.OnGlob(pattern) -} diff --git a/internal/aghtest/upstream.go b/internal/aghtest/upstream.go index 95d8f5ad..699c14b9 100644 --- a/internal/aghtest/upstream.go +++ b/internal/aghtest/upstream.go @@ -6,12 +6,18 @@ import ( "fmt" "net" "strings" - "sync" + "testing" + "github.com/AdguardTeam/golibs/errors" "github.com/miekg/dns" + "github.com/stretchr/testify/require" ) +// Additional Upstream Testing Utilities + // Upstream is a mock implementation of upstream.Upstream. +// +// TODO(a.garipov): Replace with UpstreamMock and rename it to just Upstream. type Upstream struct { // CName is a map of hostname to canonical name. CName map[string][]string @@ -25,6 +31,43 @@ type Upstream struct { Addr string } +// RespondTo returns a response with answer if req has class cl, question type +// qt, and target targ. +func RespondTo(t testing.TB, req *dns.Msg, cl, qt uint16, targ, answer string) (resp *dns.Msg) { + t.Helper() + + require.NotNil(t, req) + require.Len(t, req.Question, 1) + + q := req.Question[0] + targ = dns.Fqdn(targ) + if q.Qclass != cl || q.Qtype != qt || q.Name != targ { + return nil + } + + respHdr := dns.RR_Header{ + Name: targ, + Rrtype: qt, + Class: cl, + Ttl: 60, + } + + resp = new(dns.Msg).SetReply(req) + switch qt { + case dns.TypePTR: + resp.Answer = []dns.RR{ + &dns.PTR{ + Hdr: respHdr, + Ptr: answer, + }, + } + default: + t.Fatalf("unsupported question type: %s", dns.Type(qt)) + } + + return resp +} + // Exchange implements the upstream.Upstream interface for *Upstream. // // TODO(a.garipov): Split further into handlers. @@ -76,74 +119,57 @@ func (u *Upstream) Address() string { return u.Addr } -// TestBlockUpstream implements upstream.Upstream interface for replacing real -// upstream in tests. -type TestBlockUpstream struct { - Hostname string - - // lock protects reqNum. - lock sync.RWMutex - reqNum int - - Block bool -} - -// Exchange returns a message unique for TestBlockUpstream's Hostname-Block -// pair. -func (u *TestBlockUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) { - u.lock.Lock() - defer u.lock.Unlock() - u.reqNum++ - - hash := sha256.Sum256([]byte(u.Hostname)) - hashToReturn := hex.EncodeToString(hash[:]) - if !u.Block { - hashToReturn = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28) +// NewBlockUpstream returns an [*UpstreamMock] that works like an upstream that +// supports hash-based safe-browsing/adult-blocking feature. If shouldBlock is +// true, hostname's actual hash is returned, blocking it. Otherwise, it returns +// a different hash. +func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) { + hash := sha256.Sum256([]byte(hostname)) + hashStr := hex.EncodeToString(hash[:]) + if !shouldBlock { + hashStr = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28) } - m := &dns.Msg{} - m.SetReply(r) - m.Answer = []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: r.Question[0].Name, - }, - Txt: []string{ - hashToReturn, - }, + ans := &dns.TXT{ + Hdr: dns.RR_Header{ + Name: "", + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + Ttl: 60, + }, + Txt: []string{hashStr}, + } + respTmpl := &dns.Msg{ + Answer: []dns.RR{ans}, + } + + return &UpstreamMock{ + OnAddress: func() (addr string) { + return "sbpc.upstream.example" + }, + OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) { + resp = respTmpl.Copy() + resp.SetReply(req) + resp.Answer[0].(*dns.TXT).Hdr.Name = req.Question[0].Name + + return resp, nil }, } - - return m, nil } -// Address always returns an empty string. -func (u *TestBlockUpstream) Address() string { - return "" -} +// ErrUpstream is the error returned from the [*UpstreamMock] created by +// [NewErrorUpstream]. +const ErrUpstream errors.Error = "test upstream error" -// RequestsCount returns the number of handled requests. It's safe for -// concurrent use. -func (u *TestBlockUpstream) RequestsCount() int { - u.lock.Lock() - defer u.lock.Unlock() - - return u.reqNum -} - -// TestErrUpstream implements upstream.Upstream interface for replacing real -// upstream in tests. -type TestErrUpstream struct { - // The error returned by Exchange may be unwrapped to the Err. - Err error -} - -// Exchange always returns nil Msg and non-nil error. -func (u *TestErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) { - return nil, fmt.Errorf("errupstream: %w", u.Err) -} - -// Address always returns an empty string. -func (u *TestErrUpstream) Address() string { - return "" +// NewErrorUpstream returns an [*UpstreamMock] that returns [ErrUpstream] from +// its Exchange method. +func NewErrorUpstream() (u *UpstreamMock) { + return &UpstreamMock{ + OnAddress: func() (addr string) { + return "error.upstream.example" + }, + OnExchange: func(_ *dns.Msg) (resp *dns.Msg, err error) { + return nil, errors.Error("test upstream error") + }, + } } diff --git a/internal/dhcpd/conn_unix.go b/internal/dhcpd/conn_unix.go index 8f8e7ed5..1ed2105a 100644 --- a/internal/dhcpd/conn_unix.go +++ b/internal/dhcpd/conn_unix.go @@ -16,6 +16,8 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4/server4" "github.com/mdlayher/ethernet" + + //lint:ignore SA1019 See the TODO in go.mod. "github.com/mdlayher/raw" ) @@ -49,16 +51,15 @@ type dhcpConn struct { } // newDHCPConn creates the special connection for DHCP server. -func (s *v4Server) newDHCPConn(ifi *net.Interface) (c net.PacketConn, err error) { - // Create the raw connection. +func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err error) { var ucast net.PacketConn - if ucast, err = raw.ListenPacket(ifi, uint16(ethernet.EtherTypeIPv4), nil); err != nil { + if ucast, err = raw.ListenPacket(iface, uint16(ethernet.EtherTypeIPv4), nil); err != nil { return nil, fmt.Errorf("creating raw udp connection: %w", err) } // Create the UDP connection. var bcast net.PacketConn - bcast, err = server4.NewIPv4UDPConn(ifi.Name, &net.UDPAddr{ + bcast, err = server4.NewIPv4UDPConn(iface.Name, &net.UDPAddr{ // TODO(e.burkov): Listening on zeroes makes the server handle // requests from all the interfaces. Inspect the ways to // specify the interface-specific listening addresses. @@ -75,7 +76,7 @@ func (s *v4Server) newDHCPConn(ifi *net.Interface) (c net.PacketConn, err error) udpConn: bcast, bcastIP: s.conf.broadcastIP, rawConn: ucast, - srcMAC: ifi.HardwareAddr, + srcMAC: iface.HardwareAddr, srcIP: s.conf.dnsIPAddrs[0], }, nil } diff --git a/internal/dhcpd/conn_unix_test.go b/internal/dhcpd/conn_unix_test.go index cbcaa753..84020acd 100644 --- a/internal/dhcpd/conn_unix_test.go +++ b/internal/dhcpd/conn_unix_test.go @@ -11,9 +11,11 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/mdlayher/raw" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + //lint:ignore SA1019 See the TODO in go.mod. + "github.com/mdlayher/raw" ) func TestDHCPConn_WriteTo_common(t *testing.T) { diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 55c56c18..a085e656 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -5,11 +5,11 @@ import ( "encoding/json" "fmt" "net" - "net/http" "path/filepath" "runtime" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" ) @@ -126,7 +126,7 @@ type ServerConfig struct { ConfigModified func() `yaml:"-"` // Register an HTTP handler - HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"` + HTTPRegister aghhttp.RegisterFunc `yaml:"-"` Enabled bool `yaml:"enabled"` InterfaceName string `yaml:"interface_name"` diff --git a/internal/dhcpd/http.go b/internal/dhcpd/http.go index e340addb..a8f0c108 100644 --- a/internal/dhcpd/http.go +++ b/internal/dhcpd/http.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/errors" @@ -145,7 +146,7 @@ type dhcpServerConfigJSON struct { V4 *v4ServerConfJSON `json:"v4"` V6 *v6ServerConfJSON `json:"v6"` InterfaceName string `json:"interface_name"` - Enabled nullBool `json:"enabled"` + Enabled aghalg.NullBool `json:"enabled"` } func (s *Server) handleDHCPSetConfigV4( @@ -156,7 +157,7 @@ func (s *Server) handleDHCPSetConfigV4( } v4Conf := v4JSONToServerConf(conf.V4) - v4Conf.Enabled = conf.Enabled == nbTrue + v4Conf.Enabled = conf.Enabled == aghalg.NBTrue if len(v4Conf.RangeStart) == 0 { v4Conf.Enabled = false } @@ -183,7 +184,7 @@ func (s *Server) handleDHCPSetConfigV6( } v6Conf := v6JSONToServerConf(conf.V6) - v6Conf.Enabled = conf.Enabled == nbTrue + v6Conf.Enabled = conf.Enabled == aghalg.NBTrue if len(v6Conf.RangeStart) == 0 { v6Conf.Enabled = false } @@ -206,7 +207,7 @@ func (s *Server) handleDHCPSetConfigV6( func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { conf := &dhcpServerConfigJSON{} - conf.Enabled = boolToNullBool(s.conf.Enabled) + conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled) conf.InterfaceName = s.conf.InterfaceName err := json.NewDecoder(r.Body).Decode(conf) @@ -230,7 +231,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { return } - if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled { + if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled { aghhttp.Error(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete") return @@ -243,8 +244,8 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { return } - if conf.Enabled != nbNull { - s.conf.Enabled = conf.Enabled == nbTrue + if conf.Enabled != aghalg.NBNull { + s.conf.Enabled = conf.Enabled == aghalg.NBTrue } if conf.InterfaceName != "" { @@ -279,11 +280,11 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { type netInterfaceJSON struct { Name string `json:"name"` - GatewayIP net.IP `json:"gateway_ip"` HardwareAddr string `json:"hardware_address"` + Flags string `json:"flags"` + GatewayIP net.IP `json:"gateway_ip"` Addrs4 []net.IP `json:"ipv4_addresses"` Addrs6 []net.IP `json:"ipv6_addresses"` - Flags string `json:"flags"` } func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { @@ -497,7 +498,6 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request } ip4 := l.IP.To4() - if ip4 == nil { l.IP = l.IP.To16() diff --git a/internal/dhcpd/nullbool.go b/internal/dhcpd/nullbool.go deleted file mode 100644 index b07f6768..00000000 --- a/internal/dhcpd/nullbool.go +++ /dev/null @@ -1,58 +0,0 @@ -package dhcpd - -import ( - "bytes" - "fmt" -) - -// nullBool is a nullable boolean. Use these in JSON requests and responses -// instead of pointers to bool. -// -// TODO(a.garipov): Inspect uses of *bool, move this type into some new package -// if we need it somewhere else. -type nullBool uint8 - -// nullBool values -const ( - nbNull nullBool = iota - nbTrue - nbFalse -) - -// String implements the fmt.Stringer interface for nullBool. -func (nb nullBool) String() (s string) { - switch nb { - case nbNull: - return "null" - case nbTrue: - return "true" - case nbFalse: - return "false" - } - - return fmt.Sprintf("!invalid nullBool %d", uint8(nb)) -} - -// boolToNullBool converts a bool into a nullBool. -func boolToNullBool(cond bool) (nb nullBool) { - if cond { - return nbTrue - } - - return nbFalse -} - -// UnmarshalJSON implements the json.Unmarshaler interface for *nullBool. -func (nb *nullBool) UnmarshalJSON(b []byte) (err error) { - if len(b) == 0 || bytes.Equal(b, []byte("null")) { - *nb = nbNull - } else if bytes.Equal(b, []byte("true")) { - *nb = nbTrue - } else if bytes.Equal(b, []byte("false")) { - *nb = nbFalse - } else { - return fmt.Errorf("invalid nullBool value %q", b) - } - - return nil -} diff --git a/internal/dhcpd/nullbool_test.go b/internal/dhcpd/nullbool_test.go deleted file mode 100644 index 549df608..00000000 --- a/internal/dhcpd/nullbool_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package dhcpd - -import ( - "encoding/json" - "testing" - - "github.com/AdguardTeam/golibs/testutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNullBool_UnmarshalJSON(t *testing.T) { - testCases := []struct { - name string - wantErrMsg string - data []byte - want nullBool - }{{ - name: "empty", - wantErrMsg: "", - data: []byte{}, - want: nbNull, - }, { - name: "null", - wantErrMsg: "", - data: []byte("null"), - want: nbNull, - }, { - name: "true", - wantErrMsg: "", - data: []byte("true"), - want: nbTrue, - }, { - name: "false", - wantErrMsg: "", - data: []byte("false"), - want: nbFalse, - }, { - name: "invalid", - wantErrMsg: `invalid nullBool value "invalid"`, - data: []byte("invalid"), - want: nbNull, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var got nullBool - err := got.UnmarshalJSON(tc.data) - testutil.AssertErrorMsg(t, tc.wantErrMsg, err) - - assert.Equal(t, tc.want, got) - }) - } - - t.Run("json", func(t *testing.T) { - want := nbTrue - var got struct { - A nullBool - } - - err := json.Unmarshal([]byte(`{"A":true}`), &got) - require.NoError(t, err) - - assert.Equal(t, want, got.A) - }) -} diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 4e2a3a25..4fb85552 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -20,6 +20,9 @@ import ( "github.com/go-ping/ping" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4/server4" + "golang.org/x/exp/slices" + + //lint:ignore SA1019 See the TODO in go.mod. "github.com/mdlayher/raw" ) @@ -91,6 +94,9 @@ func (s *v4Server) validHostnameForClient(cliHostname string, ip net.IP) (hostna if hostname == "" { hostname = aghnet.GenerateHostname(ip) + } else if s.leaseHosts.Has(hostname) { + log.Info("dhcpv4: hostname %q already exists", hostname) + hostname = aghnet.GenerateHostname(ip) } err = netutil.ValidateDomainName(hostname) @@ -250,11 +256,11 @@ func (s *v4Server) rmLeaseByIndex(i int) { // Remove a dynamic lease with the same properties // Return error if a static lease is found func (s *v4Server) rmDynamicLease(lease *Lease) (err error) { - for i := 0; i < len(s.leases); i++ { - l := s.leases[i] + for i, l := range s.leases { + isStatic := l.IsStatic() - if bytes.Equal(l.HWAddr, lease.HWAddr) { - if l.IsStatic() { + if bytes.Equal(l.HWAddr, lease.HWAddr) || l.IP.Equal(lease.IP) { + if isStatic { return errors.Error("static lease already exists") } @@ -266,20 +272,7 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) { l = s.leases[i] } - if l.IP.Equal(lease.IP) { - if l.IsStatic() { - return errors.Error("static lease already exists") - } - - s.rmLeaseByIndex(i) - if i == len(s.leases) { - break - } - - l = s.leases[i] - } - - if l.Hostname == lease.Hostname { + if !isStatic && l.Hostname == lease.Hostname { l.Hostname = "" } } @@ -287,6 +280,10 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) { return nil } +// ErrDupHostname is returned by addLease when the added lease has a not empty +// non-unique hostname. +const ErrDupHostname = errors.Error("hostname is not unique") + // addLease adds a dynamic or static lease. func (s *v4Server) addLease(l *Lease) (err error) { r := s.conf.ipRange @@ -302,13 +299,17 @@ func (s *v4Server) addLease(l *Lease) (err error) { return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr) } - s.leases = append(s.leases, l) - s.leasedOffsets.set(offset, true) - if l.Hostname != "" { + if s.leaseHosts.Has(l.Hostname) { + return ErrDupHostname + } + s.leaseHosts.Add(l.Hostname) } + s.leases = append(s.leases, l) + s.leasedOffsets.set(offset, true) + return nil } @@ -333,12 +334,16 @@ func (s *v4Server) rmLease(lease *Lease) (err error) { return errors.Error("lease not found") } -// AddStaticLease adds a static lease. It is safe for concurrent use. +// AddStaticLease implements the DHCPServer interface for *v4Server. It is safe +// for concurrent use. func (s *v4Server) AddStaticLease(l *Lease) (err error) { defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }() - if ip4 := l.IP.To4(); ip4 == nil { + ip := l.IP.To4() + if ip == nil { return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP) + } else if gwIP := s.conf.GatewayIP; gwIP.Equal(ip) { + return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP) } l.Expiry = time.Unix(leaseExpireStatic, 0) @@ -359,10 +364,11 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { return fmt.Errorf("validating hostname: %w", err) } - // Don't check for hostname uniqueness, since we try to emulate - // dnsmasq here, which means that rmDynamicLease below will - // simply empty the hostname of the dynamic lease if there even - // is one. + // Don't check for hostname uniqueness, since we try to emulate dnsmasq + // here, which means that rmDynamicLease below will simply empty the + // hostname of the dynamic lease if there even is one. In case a static + // lease with the same name already exists, addLease will return an + // error and the lease won't be added. l.Hostname = hostname } @@ -377,7 +383,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { if err != nil { err = fmt.Errorf( "removing dynamic leases for %s (%s): %w", - l.IP, + ip, l.HWAddr, err, ) @@ -387,7 +393,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { err = s.addLease(l) if err != nil { - err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err) + err = fmt.Errorf("adding static lease for %s (%s): %w", ip, l.HWAddr, err) return } @@ -517,11 +523,7 @@ func (s *v4Server) findExpiredLease() int { // reserveLease reserves a lease for a client by its MAC-address. It returns // nil if it couldn't allocate a new lease. func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) { - l = &Lease{ - HWAddr: make([]byte, len(mac)), - } - - copy(l.HWAddr, mac) + l = &Lease{HWAddr: slices.Clone(mac)} l.IP = s.nextIP() if l.IP == nil { @@ -614,33 +616,25 @@ func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err erro return l, nil } -type optFQDN struct { - name string -} +// OptionFQDN returns a DHCPv4 option for sending the FQDN to the client +// requested another hostname. +// +// See https://datatracker.ietf.org/doc/html/rfc4702. +func OptionFQDN(fqdn string) (opt dhcpv4.Option) { + optData := []byte{ + // Set only S and O DHCP client FQDN option flags. + // + // See https://datatracker.ietf.org/doc/html/rfc4702#section-2.1. + 1<<0 | 1<<1, + // The RCODE fields should be set to 0xFF in the server responses. + // + // See https://datatracker.ietf.org/doc/html/rfc4702#section-2.2. + 0xFF, + 0xFF, + } + optData = append(optData, fqdn...) -func (o *optFQDN) String() string { - return "optFQDN" -} - -// flags[1] -// A-RR[1] -// PTR-RR[1] -// name[] -func (o *optFQDN) ToBytes() []byte { - b := make([]byte, 3+len(o.name)) - i := 0 - - b[i] = 0x03 // f_server_overrides | f_server - i++ - - b[i] = 255 // A-RR - i++ - - b[i] = 255 // PTR-RR - i++ - - copy(b[i:], []byte(o.name)) - return b + return dhcpv4.OptGeneric(dhcpv4.OptionFQDN, optData) } // checkLease checks if the pair of mac and ip is already leased. The mismatch @@ -673,6 +667,8 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi // processRequest is the handler for the DHCP Request request. func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) { mac := req.ClientHWAddr + // TODO(e.burkov): The IP address can only be requested in DHCPDISCOVER + // message. reqIP := req.RequestedIPAddress() if reqIP == nil { reqIP = req.ClientIPAddr @@ -705,24 +701,17 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs if !lease.IsStatic() { cliHostname := req.HostName() hostname := s.validHostnameForClient(cliHostname, reqIP) - if hostname != lease.Hostname && s.leaseHosts.Has(hostname) { - log.Info("dhcpv4: hostname %q already exists", hostname) - lease.Hostname = "" - } else { + if lease.Hostname != hostname { lease.Hostname = hostname + resp.UpdateOption(dhcpv4.OptHostName(hostname)) } s.commitLease(lease) } else if lease.Hostname != "" { - o := &optFQDN{ - name: lease.Hostname, - } - fqdn := dhcpv4.Option{ - Code: dhcpv4.OptionFQDN, - Value: o, - } - - resp.UpdateOption(fqdn) + // TODO(e.burkov): This option is used to update the server's DNS + // mapping. The option should only be answered when it has been + // requested. + resp.UpdateOption(OptionFQDN(lease.Hostname)) } resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) @@ -845,7 +834,7 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int { // TODO(a.garipov): Refactor this into handlers. var l *Lease - switch req.MessageType() { + switch mt := req.MessageType(); mt { case dhcpv4.MessageTypeDiscover: l, err = s.processDiscover(req, resp) if err != nil { diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index 58e5c27a..e720d113 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -4,16 +4,29 @@ package dhcpd import ( + "fmt" "net" "strings" "testing" + "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/testutil" "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/mdlayher/raw" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + //lint:ignore SA1019 See the TODO in go.mod. + "github.com/mdlayher/raw" +) + +var ( + DefaultRangeStart = net.IP{192, 168, 10, 100} + DefaultRangeEnd = net.IP{192, 168, 10, 200} + DefaultGatewayIP = net.IP{192, 168, 10, 1} + DefaultSelfIP = net.IP{192, 168, 10, 2} + DefaultSubnetMask = net.IP{255, 255, 255, 0} ) func notify4(flags uint32) { @@ -24,11 +37,12 @@ func notify4(flags uint32) { func defaultV4ServerConf() (conf V4ServerConf) { return V4ServerConf{ Enabled: true, - RangeStart: net.IP{192, 168, 10, 100}, - RangeEnd: net.IP{192, 168, 10, 200}, - GatewayIP: net.IP{192, 168, 10, 1}, - SubnetMask: net.IP{255, 255, 255, 0}, + RangeStart: DefaultRangeStart, + RangeEnd: DefaultRangeEnd, + GatewayIP: DefaultGatewayIP, + SubnetMask: DefaultSubnetMask, notify: notify4, + dnsIPAddrs: []net.IP{DefaultSelfIP}, } } @@ -44,44 +58,228 @@ func defaultSrv(t *testing.T) (s DHCPServer) { return s } -func TestV4_AddRemove_static(t *testing.T) { +func TestV4Server_leasing(t *testing.T) { + const ( + staticName = "static-client" + anotherName = "another-client" + ) + + staticIP := net.IP{192, 168, 10, 10} + anotherIP := DefaultRangeStart + staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} + + s := defaultSrv(t) + + t.Run("add_static", func(t *testing.T) { + err := s.AddStaticLease(&Lease{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: staticMAC, + IP: staticIP, + }) + require.NoError(t, err) + + t.Run("same_name", func(t *testing.T) { + err = s.AddStaticLease(&Lease{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: anotherMAC, + IP: anotherIP, + }) + assert.ErrorIs(t, err, ErrDupHostname) + }) + + t.Run("same_mac", func(t *testing.T) { + wantErrMsg := "dhcpv4: adding static lease: removing " + + "dynamic leases for " + anotherIP.String() + + " (" + staticMAC.String() + "): static lease already exists" + + err = s.AddStaticLease(&Lease{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: anotherName, + HWAddr: staticMAC, + IP: anotherIP, + }) + testutil.AssertErrorMsg(t, wantErrMsg, err) + }) + + t.Run("same_ip", func(t *testing.T) { + wantErrMsg := "dhcpv4: adding static lease: removing " + + "dynamic leases for " + staticIP.String() + + " (" + anotherMAC.String() + "): static lease already exists" + + err = s.AddStaticLease(&Lease{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: anotherName, + HWAddr: anotherMAC, + IP: staticIP, + }) + testutil.AssertErrorMsg(t, wantErrMsg, err) + }) + }) + + t.Run("add_dynamic", func(t *testing.T) { + s4, ok := s.(*v4Server) + require.True(t, ok) + + discoverAnOffer := func( + t *testing.T, + name string, + ip net.IP, + mac net.HardwareAddr, + ) (resp *dhcpv4.DHCPv4) { + testutil.CleanupAndRequireSuccess(t, func() (err error) { + return s.ResetLeases(s.GetLeases(LeasesStatic)) + }) + + req, err := dhcpv4.NewDiscovery( + mac, + dhcpv4.WithOption(dhcpv4.OptHostName(name)), + dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(ip)), + dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte{1, 2, 3, 4, 5, 6, 8})), + dhcpv4.WithGatewayIP(DefaultGatewayIP), + ) + require.NoError(t, err) + + resp = &dhcpv4.DHCPv4{} + res := s4.process(req, resp) + require.Positive(t, res) + require.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) + + resp.ClientHWAddr = mac + + return resp + } + + t.Run("same_name", func(t *testing.T) { + resp := discoverAnOffer(t, staticName, anotherIP, anotherMAC) + + req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption( + dhcpv4.OptHostName(staticName), + )) + require.NoError(t, err) + + res := s4.process(req, resp) + require.Positive(t, res) + + assert.Equal(t, aghnet.GenerateHostname(resp.YourIPAddr), resp.HostName()) + }) + + t.Run("same_mac", func(t *testing.T) { + resp := discoverAnOffer(t, anotherName, anotherIP, staticMAC) + + req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption( + dhcpv4.OptHostName(anotherName), + )) + require.NoError(t, err) + + res := s4.process(req, resp) + require.Positive(t, res) + + fqdnOptData := resp.Options.Get(dhcpv4.OptionFQDN) + require.Len(t, fqdnOptData, 3+len(staticName)) + assert.Equal(t, []uint8(staticName), fqdnOptData[3:]) + + assert.Equal(t, staticIP, resp.YourIPAddr) + }) + + t.Run("same_ip", func(t *testing.T) { + resp := discoverAnOffer(t, anotherName, staticIP, anotherMAC) + + req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption( + dhcpv4.OptHostName(anotherName), + )) + require.NoError(t, err) + + res := s4.process(req, resp) + require.Positive(t, res) + + assert.NotEqual(t, staticIP, resp.YourIPAddr) + }) + }) +} + +func TestV4Server_AddRemove_static(t *testing.T) { s := defaultSrv(t) ls := s.GetLeases(LeasesStatic) - assert.Empty(t, ls) + require.Empty(t, ls) - // Add static lease. - l := &Lease{ - Hostname: "static-1.local", - HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + testCases := []struct { + lease *Lease + name string + wantErrMsg string + }{{ + lease: &Lease{ + Hostname: "success.local", + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + IP: net.IP{192, 168, 10, 150}, + }, + name: "success", + wantErrMsg: "", + }, { + lease: &Lease{ + Hostname: "probably-router.local", + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + IP: DefaultGatewayIP, + }, + name: "with_gateway_ip", + wantErrMsg: "dhcpv4: adding static lease: " + + "can't assign the gateway IP 192.168.10.1 to the lease", + }, { + lease: &Lease{ + Hostname: "ip6.local", + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + IP: net.ParseIP("ffff::1"), + }, + name: "ipv6", + wantErrMsg: `dhcpv4: adding static lease: ` + + `invalid ip "ffff::1", only ipv4 is supported`, + }, { + lease: &Lease{ + Hostname: "bad-mac.local", + HWAddr: net.HardwareAddr{0xAA, 0xAA}, + IP: net.IP{192, 168, 10, 150}, + }, + name: "bad_mac", + wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` + + `bad mac address length 2, allowed: [6 8 20]`, + }, { + lease: &Lease{ + Hostname: "bad-lbl-.local", + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + IP: net.IP{192, 168, 10, 150}, + }, + name: "bad_hostname", + wantErrMsg: `dhcpv4: adding static lease: validating hostname: ` + + `bad domain name "bad-lbl-.local": ` + + `bad domain name label "bad-lbl-": bad domain name label rune '-'`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := s.AddStaticLease(tc.lease) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + if tc.wantErrMsg != "" { + return + } + + err = s.RemoveStaticLease(&Lease{ + IP: tc.lease.IP, + HWAddr: tc.lease.HWAddr, + }) + diffErrMsg := fmt.Sprintf("dhcpv4: lease for ip %s is different: %+v", tc.lease.IP, tc.lease) + testutil.AssertErrorMsg(t, diffErrMsg, err) + + // Remove static lease. + err = s.RemoveStaticLease(tc.lease) + require.NoError(t, err) + }) + + ls = s.GetLeases(LeasesStatic) + require.Emptyf(t, ls, "after %s", tc.name) } - - err := s.AddStaticLease(l) - require.NoError(t, err) - - err = s.AddStaticLease(l) - assert.Error(t, err) - - ls = s.GetLeases(LeasesStatic) - require.Len(t, ls, 1) - - assert.True(t, l.IP.Equal(ls[0].IP)) - assert.Equal(t, l.HWAddr, ls[0].HWAddr) - assert.True(t, ls[0].IsStatic()) - - // Try to remove static lease. - err = s.RemoveStaticLease(&Lease{ - IP: net.IP{192, 168, 10, 110}, - HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - }) - assert.Error(t, err) - - // Remove static lease. - err = s.RemoveStaticLease(l) - require.NoError(t, err) - ls = s.GetLeases(LeasesStatic) - assert.Empty(t, ls) } func TestV4_AddReplace(t *testing.T) { diff --git a/internal/dnsforward/access.go b/internal/dnsforward/access.go index 7a771946..33c9a978 100644 --- a/internal/dnsforward/access.go +++ b/internal/dnsforward/access.go @@ -214,7 +214,7 @@ func validateAccessSet(list *accessListJSON) (err error) { } merged := allowed.Merge(disallowed) - err = merged.Validate(aghalg.StringIsBefore) + err = merged.Validate() if err != nil { return fmt.Errorf("items in allowed and disallowed clients intersect: %w", err) } @@ -223,13 +223,13 @@ func validateAccessSet(list *accessListJSON) (err error) { } // validateStrUniq returns an informative error if clients are not unique. -func validateStrUniq(clients []string) (uc aghalg.UniqChecker, err error) { - uc = make(aghalg.UniqChecker, len(clients)) +func validateStrUniq(clients []string) (uc aghalg.UniqChecker[string], err error) { + uc = make(aghalg.UniqChecker[string], len(clients)) for _, c := range clients { uc.Add(c) } - return uc, uc.Validate(aghalg.StringIsBefore) + return uc, uc.Validate() } func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) { diff --git a/internal/dnsforward/clientid.go b/internal/dnsforward/clientid.go index 481fb84d..16bac881 100644 --- a/internal/dnsforward/clientid.go +++ b/internal/dnsforward/clientid.go @@ -65,7 +65,7 @@ func clientIDFromClientServerName( return "", err } - return clientID, nil + return strings.ToLower(clientID), nil } // clientIDFromDNSContextHTTPS extracts the client's ID from the path of the @@ -104,7 +104,7 @@ func clientIDFromDNSContextHTTPS(pctx *proxy.DNSContext) (clientID string, err e return "", fmt.Errorf("clientid check: %w", err) } - return clientID, nil + return strings.ToLower(clientID), nil } // tlsConn is a narrow interface for *tls.Conn to simplify testing. @@ -112,8 +112,8 @@ type tlsConn interface { ConnectionState() (cs tls.ConnectionState) } -// quicSession is a narrow interface for quic.Session to simplify testing. -type quicSession interface { +// quicConnection is a narrow interface for quic.Connection to simplify testing. +type quicConnection interface { ConnectionState() (cs quic.ConnectionState) } @@ -148,16 +148,16 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string cliSrvName = tc.ConnectionState().ServerName case proxy.ProtoQUIC: - qs, ok := pctx.QUICSession.(quicSession) + conn, ok := pctx.QUICConnection.(quicConnection) if !ok { return "", fmt.Errorf( - "proxy ctx quic session of proto %s is %T, want quic.Session", + "proxy ctx quic conn of proto %s is %T, want quic.Connection", proto, - pctx.QUICSession, + pctx.QUICConnection, ) } - cliSrvName = qs.ConnectionState().TLS.ServerName + cliSrvName = conn.ConnectionState().TLS.ServerName } clientID, err = clientIDFromClientServerName( diff --git a/internal/dnsforward/clientid_test.go b/internal/dnsforward/clientid_test.go index e62dbe58..31c55fcd 100644 --- a/internal/dnsforward/clientid_test.go +++ b/internal/dnsforward/clientid_test.go @@ -29,17 +29,18 @@ func (c testTLSConn) ConnectionState() (cs tls.ConnectionState) { return cs } -// testQUICSession is a quicSession for tests. -type testQUICSession struct { - // Session is embedded here simply to make testQUICSession a quic.Session - // without actually implementing all methods. - quic.Session +// testQUICConnection is a quicConnection for tests. +type testQUICConnection struct { + // Connection is embedded here simply to make testQUICConnection a + // quic.Connection without actually implementing all methods. + quic.Connection serverName string } -// ConnectionState implements the quicSession interface for testQUICSession. -func (c testQUICSession) ConnectionState() (cs quic.ConnectionState) { +// ConnectionState implements the quicConnection interface for +// testQUICConnection. +func (c testQUICConnection) ConnectionState() (cs quic.ConnectionState) { cs.TLS.ServerName = c.serverName return cs @@ -143,6 +144,22 @@ func TestServer_clientIDFromDNSContext(t *testing.T) { wantErrMsg: `clientid check: client server name "cli.myexample.com" ` + `doesn't match host server name "example.com"`, strictSNI: true, + }, { + name: "tls_case", + proto: proxy.ProtoTLS, + hostSrvName: "example.com", + cliSrvName: "InSeNsItIvE.example.com", + wantClientID: "insensitive", + wantErrMsg: ``, + strictSNI: true, + }, { + name: "quic_case", + proto: proxy.ProtoQUIC, + hostSrvName: "example.com", + cliSrvName: "InSeNsItIvE.example.com", + wantClientID: "insensitive", + wantErrMsg: ``, + strictSNI: true, }} for _, tc := range testCases { @@ -163,17 +180,17 @@ func TestServer_clientIDFromDNSContext(t *testing.T) { } } - var qs quic.Session + var qconn quic.Connection if tc.proto == proxy.ProtoQUIC { - qs = testQUICSession{ + qconn = testQUICConnection{ serverName: tc.cliSrvName, } } pctx := &proxy.DNSContext{ - Proto: tc.proto, - Conn: conn, - QUICSession: qs, + Proto: tc.proto, + Conn: conn, + QUICConnection: qconn, } clientID, err := srv.clientIDFromDNSContext(pctx) @@ -210,6 +227,11 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) { path: "/dns-query/cli/", wantClientID: "cli", wantErrMsg: "", + }, { + name: "clientid_case", + path: "/dns-query/InSeNsItIvE", + wantClientID: "insensitive", + wantErrMsg: ``, }, { name: "bad_url", path: "/foo", diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 16a6325e..d5e918c3 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -5,12 +5,12 @@ import ( "crypto/x509" "fmt" "net" - "net/http" "os" "sort" "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" @@ -134,8 +134,9 @@ type FilteringConfig struct { // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS type TLSConfig struct { - TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"` - QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"` + TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"` + QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"` + HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"` // Reject connection if the client uses server name (in SNI) that doesn't match the certificate StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` @@ -192,7 +193,7 @@ type ServerConfig struct { ConfigModified func() // Register an HTTP handler - HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) + HTTPRegister aghhttp.RegisterFunc // ResolveClients signals if the RDNS should resolve clients' addresses. ResolveClients bool @@ -277,6 +278,11 @@ func (s *Server) createProxyConfig() (proxy.Config, error) { return proxyConfig, nil } +const ( + defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com" + defaultParentalBlockHost = "family-block.dns.adguard.com" +) + // initDefaultSettings initializes default settings if nothing // is configured func (s *Server) initDefaultSettings() { @@ -288,12 +294,12 @@ func (s *Server) initDefaultSettings() { s.conf.BootstrapDNS = defaultBootstrap } - if len(s.conf.ParentalBlockHost) == 0 { - s.conf.ParentalBlockHost = parentalBlockHost + if s.conf.ParentalBlockHost == "" { + s.conf.ParentalBlockHost = defaultParentalBlockHost } - if len(s.conf.SafeBrowsingBlockHost) == 0 { - s.conf.SafeBrowsingBlockHost = safeBrowsingBlockHost + if s.conf.SafeBrowsingBlockHost == "" { + s.conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost } if s.conf.UDPListenAddrs == nil { diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 19d54d91..f3c98361 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -100,9 +100,9 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { s.processInitial, s.processDDRQuery, s.processDetermineLocal, - s.processInternalHosts, + s.processDHCPHosts, s.processRestrictLocal, - s.processInternalIPAddrs, + s.processDHCPAddrs, s.processFilteringBeforeRequest, s.processLocalPTR, s.processUpstream, @@ -230,12 +230,10 @@ func (s *Server) onDHCPLeaseChanged(flags int) { ) } - lowhost := strings.ToLower(l.Hostname) + lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix) + ip := netutil.CloneIP(l.IP) - ipToHost.Set(l.IP, lowhost) - - ip := make(net.IP, 4) - copy(ip, l.IP.To4()) + ipToHost.Set(ip, lowhost) hostToIP[lowhost] = ip } @@ -260,9 +258,8 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) { } if question.Name == ddrHostFQDN { - // TODO(a.garipov): Check DoQ support in next RFC drafts. - if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil || - question.Qtype != dns.TypeSVCB { + if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil && + s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB { d.Res = s.makeResponse(d.Req) return resultCodeFinish @@ -276,12 +273,18 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) { return resultCodeSuccess } -// makeDDRResponse creates DDR answer according to server configuration. +// makeDDRResponse creates DDR answer according to server configuration. The +// contructed SVCB resource records have the priority of 1 for each entry, +// similar to examples provided by https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html. +// +// TODO(a.meshkov): Consider setting the priority values based on the protocol. func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) { resp = s.makeResponse(req) - domainName := s.conf.ServerName + // TODO(e.burkov): Think about storing the FQDN version of the server's + // name somewhere. + domainName := dns.Fqdn(s.conf.ServerName) - for _, addr := range s.dnsProxy.HTTPSListenAddr { + for _, addr := range s.conf.HTTPSListenAddrs { values := []dns.SVCBKeyValue{ &dns.SVCBAlpn{Alpn: []string{"h2"}}, &dns.SVCBPort{Port: uint16(addr.Port)}, @@ -306,7 +309,23 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) { ans := &dns.SVCB{ Hdr: s.hdr(req, dns.TypeSVCB), - Priority: 2, + Priority: 1, + Target: domainName, + Value: values, + } + + resp.Answer = append(resp.Answer, ans) + } + + for _, addr := range s.dnsProxy.QUICListenAddr { + values := []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"doq"}}, + &dns.SVCBPort{Port: uint16(addr.Port)}, + } + + ans := &dns.SVCB{ + Hdr: s.hdr(req, dns.TypeSVCB), + Priority: 1, Target: domainName, Value: values, } @@ -355,11 +374,11 @@ func (s *Server) hostToIP(host string) (ip net.IP, ok bool) { return ip, true } -// processInternalHosts respond to A requests if the target hostname is known to +// processDHCPHosts respond to A requests if the target hostname is known to // the server. // // TODO(a.garipov): Adapt to AAAA as well. -func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) { +func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) { if !s.dhcpServer.Enabled() { return resultCodeSuccess } @@ -374,11 +393,10 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) { return resultCodeSuccess } - reqHost := strings.ToLower(q.Name) + reqHost := strings.ToLower(q.Name[:len(q.Name)-1]) // TODO(a.garipov): Move everything related to DHCP local domain to the DHCP // server. - host := strings.TrimSuffix(reqHost, s.localDomainSuffix) - if host == reqHost { + if !strings.HasSuffix(reqHost, s.localDomainSuffix) { return resultCodeSuccess } @@ -391,7 +409,7 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) { return resultCodeFinish } - ip, ok := s.hostToIP(host) + ip, ok := s.hostToIP(reqHost) if !ok { // TODO(e.burkov): Inspect special cases when user want to apply some // rules handled by other processors to the hosts with TLD. @@ -448,7 +466,7 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) { // Restrict an access to local addresses for external clients. We also // assume that all the DHCP leases we give are locally-served or at least - // don't need to be inaccessible externally. + // don't need to be accessible externally. if !s.privateNets.Contains(ip) { log.Debug("dns: addr %s is not from locally-served network", ip) @@ -488,7 +506,7 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) { return "", false } - var v interface{} + var v any v, ok = s.tableIPToHost.Get(ip) if !ok { return "", false @@ -505,7 +523,7 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) { // Respond to PTR requests if the target IP is leased by our DHCP server and the // requestor is inside the local network. -func (s *Server) processInternalIPAddrs(ctx *dnsContext) (rc resultCode) { +func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) { d := ctx.proxyCtx if d.Res != nil { return resultCodeSuccess diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index 8ab7501c..25e28afd 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -14,12 +14,15 @@ import ( "github.com/stretchr/testify/require" ) -const ddrTestDomainName = "dns.example.net" +const ( + ddrTestDomainName = "dns.example.net" + ddrTestFQDN = ddrTestDomainName + "." +) func TestServer_ProcessDDRQuery(t *testing.T) { dohSVCB := &dns.SVCB{ Priority: 1, - Target: ddrTestDomainName, + Target: ddrTestFQDN, Value: []dns.SVCBKeyValue{ &dns.SVCBAlpn{Alpn: []string{"h2"}}, &dns.SVCBPort{Port: 8044}, @@ -28,14 +31,23 @@ func TestServer_ProcessDDRQuery(t *testing.T) { } dotSVCB := &dns.SVCB{ - Priority: 2, - Target: ddrTestDomainName, + Priority: 1, + Target: ddrTestFQDN, Value: []dns.SVCBKeyValue{ &dns.SVCBAlpn{Alpn: []string{"dot"}}, &dns.SVCBPort{Port: 8043}, }, } + doqSVCB := &dns.SVCB{ + Priority: 1, + Target: ddrTestFQDN, + Value: []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"doq"}}, + &dns.SVCBPort{Port: 8042}, + }, + } + testCases := []struct { name string host string @@ -43,6 +55,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) { wantRes resultCode portDoH int portDoT int + portDoQ int qtype uint16 ddrEnabled bool }{{ @@ -88,6 +101,14 @@ func TestServer_ProcessDDRQuery(t *testing.T) { qtype: dns.TypeSVCB, ddrEnabled: true, portDoH: 8044, + }, { + name: "doq", + wantRes: resultCodeFinish, + want: []*dns.SVCB{doqSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoQ: 8042, }, { name: "dot_doh", wantRes: resultCodeFinish, @@ -101,7 +122,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.ddrEnabled) + s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.portDoQ, tc.ddrEnabled) req := createTestMessageWithType(tc.host, tc.qtype) @@ -130,19 +151,19 @@ func TestServer_ProcessDDRQuery(t *testing.T) { } } -func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s *Server) { +func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) { t.Helper() proxyConf := proxy.Config{} - if portDoH > 0 { - proxyConf.HTTPSListenAddr = []*net.TCPAddr{{Port: portDoH}} - } - if portDoT > 0 { proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}} } + if portDoQ > 0 { + proxyConf.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}} + } + s = &Server{ dnsProxy: &proxy.Proxy{ Config: proxyConf, @@ -157,6 +178,10 @@ func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s * }, } + if portDoH > 0 { + s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}} + } + return s } @@ -204,7 +229,7 @@ func TestServer_ProcessDetermineLocal(t *testing.T) { } } -func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { +func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { knownIP := net.IP{1, 2, 3, 4} testCases := []struct { @@ -245,7 +270,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { dhcpServer: &testDHCP{}, localDomainSuffix: defaultLocalDomainSuffix, tableHostToIP: hostToIPTable{ - "example": knownIP, + "example." + defaultLocalDomainSuffix: knownIP, }, } @@ -267,7 +292,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { isLocalClient: tc.isLocalCli, } - res := s.processInternalHosts(dctx) + res := s.processDHCPHosts(dctx) require.Equal(t, tc.wantRes, res) pctx := dctx.proxyCtx if tc.wantRes == resultCodeFinish { @@ -293,10 +318,10 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { } } -func TestServer_ProcessInternalHosts(t *testing.T) { +func TestServer_ProcessDHCPHosts(t *testing.T) { const ( examplecom = "example.com" - examplelan = "example.lan" + examplelan = "example." + defaultLocalDomainSuffix ) knownIP := net.IP{1, 2, 3, 4} @@ -345,41 +370,41 @@ func TestServer_ProcessInternalHosts(t *testing.T) { }, { name: "success_custom_suffix", host: "example.custom", - suffix: ".custom.", + suffix: "custom", wantIP: knownIP, wantRes: resultCodeSuccess, qtyp: dns.TypeA, }} for _, tc := range testCases { + s := &Server{ + dhcpServer: &testDHCP{}, + localDomainSuffix: tc.suffix, + tableHostToIP: hostToIPTable{ + "example." + tc.suffix: knownIP, + }, + } + + req := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: 1234, + }, + Question: []dns.Question{{ + Name: dns.Fqdn(tc.host), + Qtype: tc.qtyp, + Qclass: dns.ClassINET, + }}, + } + + dctx := &dnsContext{ + proxyCtx: &proxy.DNSContext{ + Req: req, + }, + isLocalClient: true, + } + t.Run(tc.name, func(t *testing.T) { - s := &Server{ - dhcpServer: &testDHCP{}, - localDomainSuffix: tc.suffix, - tableHostToIP: hostToIPTable{ - "example": knownIP, - }, - } - - req := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: 1234, - }, - Question: []dns.Question{{ - Name: dns.Fqdn(tc.host), - Qtype: tc.qtyp, - Qclass: dns.ClassINET, - }}, - } - - dctx := &dnsContext{ - proxyCtx: &proxy.DNSContext{ - Req: req, - }, - isLocalClient: true, - } - - res := s.processInternalHosts(dctx) + res := s.processDHCPHosts(dctx) pctx := dctx.proxyCtx assert.Equal(t, tc.wantRes, res) if tc.wantRes == resultCodeFinish { diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index c0cd0e55..ca479fc4 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -33,11 +33,6 @@ const DefaultTimeout = 10 * time.Second // requests between the BeforeRequestHandler stage and the actual processing. const defaultClientIDCacheCount = 1024 -const ( - safeBrowsingBlockHost = "standard-block.dns.adguard.com" - parentalBlockHost = "family-block.dns.adguard.com" -) - var defaultDNS = []string{ "https://dns10.quad9.net/dns-query", } @@ -66,7 +61,7 @@ type Server struct { dnsFilter *filtering.DNSFilter // DNS filter instance dhcpServer dhcpd.ServerInterface // DHCP server instance (optional) queryLog querylog.QueryLog // Query log instance - stats stats.Stats + stats stats.Interface access *accessCtx // localDomainSuffix is the suffix used to detect internal hosts. It @@ -107,12 +102,12 @@ type Server struct { // when no suffix is provided. // // See the documentation for Server.localDomainSuffix. -const defaultLocalDomainSuffix = ".lan." +const defaultLocalDomainSuffix = "lan" // DNSCreateParams are parameters to create a new server. type DNSCreateParams struct { DNSFilter *filtering.DNSFilter - Stats stats.Stats + Stats stats.Interface QueryLog querylog.QueryLog DHCPServer dhcpd.ServerInterface PrivateNets netutil.SubnetSet @@ -120,17 +115,6 @@ type DNSCreateParams struct { LocalDomain string } -// domainNameToSuffix converts a domain name into a local domain suffix. -func domainNameToSuffix(tld string) (suffix string) { - l := len(tld) + 2 - b := make([]byte, l) - b[0] = '.' - copy(b[1:], tld) - b[l-1] = '.' - - return string(b) -} - const ( // recursionTTL is the time recursive request is cached for. recursionTTL = 1 * time.Second @@ -151,7 +135,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) { return nil, fmt.Errorf("local domain: %w", err) } - localDomainSuffix = domainNameToSuffix(p.LocalDomain) + localDomainSuffix = p.LocalDomain } if p.Anonymizer == nil { diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 36761f41..5144680b 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -17,13 +17,13 @@ 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/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" - "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/timeutil" @@ -853,10 +853,7 @@ func TestBlockedByHosts(t *testing.T) { func TestBlockedBySafeBrowsing(t *testing.T) { const hostname = "wmconvirus.narod.ru" - sbUps := &aghtest.TestBlockUpstream{ - Hostname: hostname, - Block: true, - } + sbUps := aghtest.NewBlockUpstream(hostname, true) ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname) filterConf := &filtering.Config{ @@ -988,7 +985,7 @@ func TestRewrite(t *testing.T) { } } -func publicKey(priv interface{}) interface{} { +func publicKey(priv any) any { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey @@ -1016,31 +1013,33 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} func TestPTRResponseFromDHCPLeases(t *testing.T) { + const localDomain = "lan" + s, err := NewServer(DNSCreateParams{ DNSFilter: filtering.New(&filtering.Config{}, nil), DHCPServer: &testDHCP{}, PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), + LocalDomain: localDomain, }) require.NoError(t, err) s.conf.UDPListenAddrs = []*net.UDPAddr{{}} s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} - s.conf.FilteringConfig.ProtectionEnabled = true + s.conf.ProtectionEnabled = true err = s.Prepare(nil) require.NoError(t, err) err = s.Start() require.NoError(t, err) - t.Cleanup(s.Close) addr := s.dnsProxy.Addr(proxy.ProtoUDP) req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR) resp, err := dns.Exchange(req, addr.String()) - require.NoError(t, err) + require.NoErrorf(t, err, "%s", addr) require.Len(t, resp.Answer, 1) @@ -1049,7 +1048,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { ptr, ok := resp.Answer[0].(*dns.PTR) require.True(t, ok) - assert.Equal(t, "myhost.", ptr.Ptr) + assert.Equal(t, dns.Fqdn("myhost."+localDomain), ptr.Ptr) } func TestPTRResponseFromHosts(t *testing.T) { @@ -1175,25 +1174,48 @@ func TestNewServer(t *testing.T) { } func TestServer_Exchange(t *testing.T) { - extUpstream := &aghtest.Upstream{ - Reverse: map[string][]string{ - "1.1.1.1.in-addr.arpa.": {"one.one.one.one"}, + const ( + onesHost = "one.one.one.one" + localDomainHost = "local.domain" + ) + + var ( + onesIP = net.IP{1, 1, 1, 1} + localIP = net.IP{192, 168, 1, 1} + ) + + revExtIPv4, err := netutil.IPToReversedAddr(onesIP) + 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) { + resp = aghalg.Coalesce( + aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revExtIPv4, onesHost), + new(dns.Msg).SetRcode(req, dns.RcodeNameError), + ) + + return resp, nil }, } - locUpstream := &aghtest.Upstream{ - Reverse: map[string][]string{ - "1.1.168.192.in-addr.arpa.": {"local.domain"}, - "2.1.168.192.in-addr.arpa.": {}, + + revLocIPv4, err := netutil.IPToReversedAddr(localIP) + 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) { + resp = aghalg.Coalesce( + aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revLocIPv4, localDomainHost), + new(dns.Msg).SetRcode(req, dns.RcodeNameError), + ) + + return resp, nil }, } - upstreamErr := errors.Error("upstream error") - errUpstream := &aghtest.TestErrUpstream{ - Err: upstreamErr, - } - nonPtrUpstream := &aghtest.TestBlockUpstream{ - Hostname: "some-host", - Block: true, - } + + errUpstream := aghtest.NewErrorUpstream() + nonPtrUpstream := aghtest.NewBlockUpstream("some-host", true) srv := NewCustomServer(&proxy.Proxy{ Config: proxy.Config{ @@ -1207,7 +1229,6 @@ func TestServer_Exchange(t *testing.T) { srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed) - localIP := net.IP{192, 168, 1, 1} testCases := []struct { name string want string @@ -1216,20 +1237,20 @@ func TestServer_Exchange(t *testing.T) { req net.IP }{{ name: "external_good", - want: "one.one.one.one", + want: onesHost, wantErr: nil, locUpstream: nil, - req: net.IP{1, 1, 1, 1}, + req: onesIP, }, { name: "local_good", - want: "local.domain", + want: localDomainHost, wantErr: nil, locUpstream: locUpstream, req: localIP, }, { name: "upstream_error", want: "", - wantErr: upstreamErr, + wantErr: aghtest.ErrUpstream, locUpstream: errUpstream, req: localIP, }, { diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 2b7cfd13..93121fac 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/http" - "sort" "strings" "time" @@ -17,6 +16,8 @@ import ( "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" "github.com/miekg/dns" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" ) type dnsConfig struct { @@ -363,6 +364,21 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro return nil, nil } + for _, u := range upstreams { + var ups string + var domains []string + ups, domains, err = separateUpstream(u) + if err != nil { + // Don't wrap the error since it's informative enough as is. + return nil, err + } + + _, err = validateUpstream(ups, domains) + if err != nil { + return nil, fmt.Errorf("validating upstream %q: %w", u, err) + } + } + conf, err = proxy.ParseUpstreamsConfig( upstreams, &upstream.Options{Bootstrap: []string{}, Timeout: DefaultTimeout}, @@ -373,13 +389,6 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro return nil, errors.Error("no default upstreams specified") } - for _, u := range upstreams { - _, err = validateUpstream(u) - if err != nil { - return nil, err - } - } - return conf, nil } @@ -393,20 +402,6 @@ func ValidateUpstreams(upstreams []string) (err error) { return err } -// stringKeysSorted returns the sorted slice of string keys of m. -// -// TODO(e.burkov): Use generics in Go 1.18. Move into golibs. -func stringKeysSorted(m map[string][]upstream.Upstream) (sorted []string) { - sorted = make([]string, 0, len(m)) - for s := range m { - sorted = append(sorted, s) - } - - sort.Strings(sorted) - - return sorted -} - // ValidateUpstreamsPrivate validates each upstream and returns an error if any // upstream is invalid or if there are no default upstreams specified. It also // checks each domain of domain-specific upstreams for being ARPA pointing to @@ -421,9 +416,11 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) return nil } - var errs []error + keys := maps.Keys(conf.DomainReservedUpstreams) + slices.Sort(keys) - for _, domain := range stringKeysSorted(conf.DomainReservedUpstreams) { + var errs []error + for _, domain := range keys { var subnet *net.IPNet subnet, err = netutil.SubnetFromReversedAddr(domain) if err != nil { @@ -449,16 +446,14 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"} -func validateUpstream(u string) (useDefault bool, err error) { - // Check if the user tries to specify upstream for domain. - var isDomainSpec bool - u, isDomainSpec, err = separateUpstream(u) - if err != nil { - return !isDomainSpec, err - } - +// validateUpstream returns an error if u alongside with domains is not a valid +// upstream configuration. useDefault is true if the upstream is +// domain-specific and is configured to point at the default upstream server +// which is validated separately. The upstream is considered domain-specific +// only if domains is at least not nil. +func validateUpstream(u string, domains []string) (useDefault bool, err error) { // The special server address '#' means that default server must be used. - if useDefault = !isDomainSpec; u == "#" && isDomainSpec { + if useDefault = u == "#" && domains != nil; useDefault { return useDefault, nil } @@ -485,12 +480,14 @@ func validateUpstream(u string) (useDefault bool, err error) { return useDefault, nil } -// separateUpstream returns the upstream without the specified domains. -// isDomainSpec is true when the upstream is domains-specific. -func separateUpstream(upstreamStr string) (upstream string, isDomainSpec bool, err error) { +// separateUpstream returns the upstream and the specified domains. domains is +// nil when the upstream is not domains-specific. Otherwise it may also be +// empty. +func separateUpstream(upstreamStr string) (ups string, domains []string, err error) { if !strings.HasPrefix(upstreamStr, "[/") { - return upstreamStr, false, nil + return upstreamStr, nil, nil } + defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }() parts := strings.Split(upstreamStr[2:], "/]") @@ -498,39 +495,46 @@ func separateUpstream(upstreamStr string) (upstream string, isDomainSpec bool, e case 2: // Go on. case 1: - return "", false, errors.Error("missing separator") + return "", nil, errors.Error("missing separator") default: - return "", true, errors.Error("duplicated separator") + return "", []string{}, errors.Error("duplicated separator") } - var domains string - domains, upstream = parts[0], parts[1] - for i, host := range strings.Split(domains, "/") { + for i, host := range strings.Split(parts[0], "/") { if host == "" { continue } - err = netutil.ValidateDomainName(host) + err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*.")) if err != nil { - return "", true, fmt.Errorf("domain at index %d: %w", i, err) + return "", domains, fmt.Errorf("domain at index %d: %w", i, err) } + + domains = append(domains, host) } - return upstream, true, nil + return parts[1], domains, nil } -// excFunc is a signature of function to check if upstream exchanges correctly. -type excFunc func(u upstream.Upstream) (err error) +// healthCheckFunc is a signature of function to check if upstream exchanges +// properly. +type healthCheckFunc func(u upstream.Upstream) (err error) // checkDNSUpstreamExc checks if the DNS upstream exchanges correctly. func checkDNSUpstreamExc(u upstream.Upstream) (err error) { + // testTLD is the special-use fully-qualified domain name for testing the + // DNS server reachability. + // + // See https://datatracker.ietf.org/doc/html/rfc6761#section-6.2. + const testTLD = "test." + req := &dns.Msg{ MsgHdr: dns.MsgHdr{ Id: dns.Id(), RecursionDesired: true, }, Question: []dns.Question{{ - Name: "google-public-dns-a.google.com.", + Name: testTLD, Qtype: dns.TypeA, Qclass: dns.ClassINET, }}, @@ -540,12 +544,8 @@ func checkDNSUpstreamExc(u upstream.Upstream) (err error) { reply, err = u.Exchange(req) if err != nil { return fmt.Errorf("couldn't communicate with upstream: %w", err) - } - - if len(reply.Answer) != 1 { - return fmt.Errorf("wrong response") - } else if a, ok := reply.Answer[0].(*dns.A); !ok || !a.A.Equal(net.IP{8, 8, 8, 8}) { - return fmt.Errorf("wrong response") + } else if len(reply.Answer) != 0 { + return errors.Error("wrong response") } return nil @@ -553,14 +553,22 @@ func checkDNSUpstreamExc(u upstream.Upstream) (err error) { // checkPrivateUpstreamExc checks if the upstream for resolving private // addresses exchanges correctly. +// +// TODO(e.burkov): Think about testing the ip6.arpa. as well. func checkPrivateUpstreamExc(u upstream.Upstream) (err error) { + // inAddrArpaTLD is the special-use fully-qualified domain name for PTR IP + // address resolution. + // + // See https://datatracker.ietf.org/doc/html/rfc1035#section-3.5. + const inAddrArpaTLD = "in-addr.arpa." + req := &dns.Msg{ MsgHdr: dns.MsgHdr{ Id: dns.Id(), RecursionDesired: true, }, Question: []dns.Question{{ - Name: "1.0.0.127.in-addr.arpa.", + Name: inAddrArpaTLD, Qtype: dns.TypePTR, Qclass: dns.ClassINET, }}, @@ -573,46 +581,66 @@ func checkPrivateUpstreamExc(u upstream.Upstream) (err error) { return nil } -func checkDNS(input string, bootstrap []string, timeout time.Duration, ef excFunc) (err error) { - if IsCommentOrEmpty(input) { +// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark +// the tested upstream domain-specific and therefore consider its errors +// non-critical. +// +// TODO(a.garipov): Some common mechanism of distinguishing between errors and +// warnings (non-critical errors) is desired. +type domainSpecificTestError struct { + error +} + +// checkDNS checks the upstream server defined by upstreamConfigStr using +// healthCheck for actually exchange messages. It uses bootstrap to resolve the +// upstream's address. +func checkDNS( + upstreamConfigStr string, + bootstrap []string, + timeout time.Duration, + healthCheck healthCheckFunc, +) (err error) { + if IsCommentOrEmpty(upstreamConfigStr) { return nil } // Separate upstream from domains list. - var useDefault bool - if useDefault, err = validateUpstream(input); err != nil { + upstreamAddr, domains, err := separateUpstream(upstreamConfigStr) + if err != nil { return fmt.Errorf("wrong upstream format: %w", err) } - // No need to check this DNS server. - if !useDefault { + useDefault, err := validateUpstream(upstreamAddr, domains) + if err != nil { + return fmt.Errorf("wrong upstream format: %w", err) + } else if useDefault { return nil } - if input, _, err = separateUpstream(input); err != nil { - return fmt.Errorf("wrong upstream format: %w", err) - } - if len(bootstrap) == 0 { bootstrap = defaultBootstrap } - log.Debug("checking if upstream %s works", input) + log.Debug("dnsforward: checking if upstream %q works", upstreamAddr) - var u upstream.Upstream - u, err = upstream.AddressToUpstream(input, &upstream.Options{ + u, err := upstream.AddressToUpstream(upstreamAddr, &upstream.Options{ Bootstrap: bootstrap, Timeout: timeout, }) if err != nil { - return fmt.Errorf("failed to choose upstream for %q: %w", input, err) + return fmt.Errorf("failed to choose upstream for %q: %w", upstreamAddr, err) } - if err = ef(u); err != nil { - return fmt.Errorf("upstream %q fails to exchange: %w", input, err) + if err = healthCheck(u); err != nil { + err = fmt.Errorf("upstream %q fails to exchange: %w", upstreamAddr, err) + if domains != nil { + return domainSpecificTestError{error: err} + } + + return err } - log.Debug("upstream %s is ok", input) + log.Debug("dnsforward: upstream %q is ok", upstreamAddr) return nil } @@ -635,6 +663,9 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { if err != nil { log.Info("%v", err) result[host] = err.Error() + if _, ok := err.(domainSpecificTestError); ok { + result[host] = fmt.Sprintf("WARNING: %s", result[host]) + } continue } @@ -650,6 +681,9 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { // above, we rewriting the error for it. These cases should be // handled properly instead. result[host] = err.Error() + if _, ok := err.(domainSpecificTestError); ok { + result[host] = fmt.Sprintf("WARNING: %s", result[host]) + } continue } diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index f468f7ae..8863c6c6 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -34,7 +34,7 @@ func (fsr *fakeSystemResolvers) Get() (rs []string) { return nil } -func loadTestData(t *testing.T, casesFileName string, cases interface{}) { +func loadTestData(t *testing.T, casesFileName string, cases any) { t.Helper() var f *os.File @@ -185,7 +185,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { wantSet: "", }, { name: "upstream_dns_bad", - wantSet: `validating upstream servers: bad ipport address "!!!": ` + + wantSet: `validating upstream servers: ` + + `validating upstream "!!!": bad ipport address "!!!": ` + `address !!!: missing port in address`, }, { name: "bootstraps_bad", @@ -256,112 +257,6 @@ func TestIsCommentOrEmpty(t *testing.T) { } } -func TestValidateUpstream(t *testing.T) { - testCases := []struct { - wantDef assert.BoolAssertionFunc - name string - upstream string - wantErr string - }{{ - wantDef: assert.True, - name: "invalid", - upstream: "1.2.3.4.5", - wantErr: `bad ipport address "1.2.3.4.5": address 1.2.3.4.5: missing port in address`, - }, { - wantDef: assert.True, - name: "invalid", - upstream: "123.3.7m", - wantErr: `bad ipport address "123.3.7m": address 123.3.7m: missing port in address`, - }, { - wantDef: assert.True, - name: "invalid", - upstream: "htttps://google.com/dns-query", - wantErr: `wrong protocol`, - }, { - wantDef: assert.True, - name: "invalid", - upstream: "[/host.com]tls://dns.adguard.com", - wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`, - }, { - wantDef: assert.True, - name: "invalid", - upstream: "[host.ru]#", - wantErr: `bad ipport address "[host.ru]#": address [host.ru]#: missing port in address`, - }, { - wantDef: assert.True, - name: "valid_default", - upstream: "1.1.1.1", - wantErr: ``, - }, { - wantDef: assert.True, - name: "valid_default", - upstream: "tls://1.1.1.1", - wantErr: ``, - }, { - wantDef: assert.True, - name: "valid_default", - upstream: "https://dns.adguard.com/dns-query", - wantErr: ``, - }, { - wantDef: assert.True, - name: "valid_default", - upstream: "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", - wantErr: ``, - }, { - wantDef: assert.True, - name: "default_udp_host", - upstream: "udp://dns.google", - }, { - wantDef: assert.True, - name: "default_udp_ip", - upstream: "udp://8.8.8.8", - }, { - wantDef: assert.False, - name: "valid", - upstream: "[/host.com/]1.1.1.1", - wantErr: ``, - }, { - wantDef: assert.False, - name: "valid", - upstream: "[//]tls://1.1.1.1", - wantErr: ``, - }, { - wantDef: assert.False, - name: "valid", - upstream: "[/www.host.com/]#", - wantErr: ``, - }, { - wantDef: assert.False, - name: "valid", - upstream: "[/host.com/google.com/]8.8.8.8", - wantErr: ``, - }, { - wantDef: assert.False, - name: "valid", - upstream: "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", - wantErr: ``, - }, { - wantDef: assert.False, - name: "idna", - upstream: "[/пример.рф/]8.8.8.8", - wantErr: ``, - }, { - wantDef: assert.False, - name: "bad_domain", - upstream: "[/!/]8.8.8.8", - wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` + - `bad domain name "!": bad domain name label "!": bad domain name label rune '!'`, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - defaultUpstream, err := validateUpstream(tc.upstream) - testutil.AssertErrorMsg(t, tc.wantErr, err) - tc.wantDef(t, defaultUpstream) - }) - } -} - func TestValidateUpstreams(t *testing.T) { testCases := []struct { name string @@ -376,7 +271,7 @@ func TestValidateUpstreams(t *testing.T) { wantErr: ``, set: []string{"# comment"}, }, { - name: "valid_no_default", + name: "no_default", wantErr: `no default upstreams specified`, set: []string{ "[/host.com/]1.1.1.1", @@ -386,7 +281,7 @@ func TestValidateUpstreams(t *testing.T) { "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", }, }, { - name: "valid_with_default", + name: "with_default", wantErr: ``, set: []string{ "[/host.com/]1.1.1.1", @@ -398,8 +293,46 @@ func TestValidateUpstreams(t *testing.T) { }, }, { name: "invalid", - wantErr: `cannot prepare the upstream dhcp://fake.dns ([]): unsupported url scheme: dhcp`, + wantErr: `validating upstream "dhcp://fake.dns": wrong protocol`, set: []string{"dhcp://fake.dns"}, + }, { + name: "invalid", + wantErr: `validating upstream "1.2.3.4.5": bad ipport address "1.2.3.4.5": address 1.2.3.4.5: missing port in address`, + set: []string{"1.2.3.4.5"}, + }, { + name: "invalid", + wantErr: `validating upstream "123.3.7m": bad ipport address "123.3.7m": address 123.3.7m: missing port in address`, + set: []string{"123.3.7m"}, + }, { + name: "invalid", + wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`, + set: []string{"[/host.com]tls://dns.adguard.com"}, + }, { + name: "invalid", + wantErr: `validating upstream "[host.ru]#": bad ipport address "[host.ru]#": address [host.ru]#: missing port in address`, + set: []string{"[host.ru]#"}, + }, { + name: "valid_default", + wantErr: ``, + set: []string{ + "1.1.1.1", + "tls://1.1.1.1", + "https://dns.adguard.com/dns-query", + "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + "udp://dns.google", + "udp://8.8.8.8", + "[/host.com/]1.1.1.1", + "[//]tls://1.1.1.1", + "[/www.host.com/]#", + "[/host.com/google.com/]8.8.8.8", + "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + "[/пример.рф/]8.8.8.8", + }, + }, { + name: "bad_domain", + wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` + + `bad domain name "!": bad domain name label "!": bad domain name label rune '!'`, + set: []string{"[/!/]8.8.8.8"}, }} for _, tc := range testCases { diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index 56cc19c5..9a7b1ddb 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -64,9 +64,9 @@ func (s *Server) logQuery( Answer: pctx.Res, OrigAnswer: dctx.origResp, Result: dctx.result, - Elapsed: elapsed, ClientID: dctx.clientID, ClientIP: ip, + Elapsed: elapsed, AuthenticatedData: dctx.responseAD, } diff --git a/internal/dnsforward/stats_test.go b/internal/dnsforward/stats_test.go index fdaa3678..d991be12 100644 --- a/internal/dnsforward/stats_test.go +++ b/internal/dnsforward/stats_test.go @@ -34,7 +34,7 @@ func (l *testQueryLog) Add(p *querylog.AddParams) { type testStats struct { // Stats is embedded here simply to make testStats a stats.Stats without // actually implementing all methods. - stats.Stats + stats.Interface lastEntry stats.Entry } diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go index bf990de9..41e8ad04 100644 --- a/internal/filtering/blocked.go +++ b/internal/filtering/blocked.go @@ -20,8 +20,12 @@ type svc struct { // client/src/helpers/constants.js // client/src/components/ui/Icons.js var serviceRulesArray = []svc{{ - name: "whatsapp", - rules: []string{"||whatsapp.net^", "||whatsapp.com^"}, + name: "whatsapp", + rules: []string{ + "||wa.me^", + "||whatsapp.com^", + "||whatsapp.net^", + }, }, { name: "facebook", rules: []string{ @@ -31,29 +35,43 @@ var serviceRulesArray = []svc{{ "||accountkit.com^", "||fb.me^", "||fb.com^", + "||fb.gg^", "||fbsbx.com^", + "||fbwat.ch^", "||messenger.com^", "||facebookcorewwwi.onion^", "||fbcdn.com^", "||fb.watch^", }, }, { - name: "twitter", - rules: []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}, + name: "twitter", + rules: []string{ + "||t.co^", + "||twimg.com^", + "||twitter.com^", + "||twttr.com^", + }, }, { name: "youtube", rules: []string{ - "||youtube.com^", - "||ytimg.com^", - "||youtu.be^", "||googlevideo.com^", - "||youtubei.googleapis.com^", - "||youtube-nocookie.com^", + "||wide-youtube.l.google.com^", + "||youtu.be^", "||youtube", + "||youtube-nocookie.com^", + "||youtube.com^", + "||youtubei.googleapis.com^", + "||youtubekids.com^", + "||ytimg.com^", }, }, { - name: "twitch", - rules: []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}, + name: "twitch", + rules: []string{ + "||jtvnw.net^", + "||ttvnw.net^", + "||twitch.tv^", + "||twitchcdn.net^", + }, }, { name: "netflix", rules: []string{ @@ -83,20 +101,36 @@ var serviceRulesArray = []svc{{ "||discordapp.net^", "||discordapp.com^", "||discord.com^", + "||discord.gift", "||discord.media^", }, }, { name: "ok", rules: []string{"||ok.ru^"}, }, { - name: "skype", - rules: []string{"||skype.com^", "||skypeassets.com^"}, + name: "skype", + rules: []string{ + "||edge-skype-com.s-0001.s-msedge.net^", + "||skype-edf.akadns.net^", + "||skype.com^", + "||skypeassets.com^", + "||skypedata.akadns.net^", + }, }, { - name: "vk", - rules: []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}, + name: "vk", + rules: []string{ + "||userapi.com^", + "||vk-cdn.net^", + "||vk.com^", + "||vkuservideo.net^", + }, }, { - name: "origin", - rules: []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}, + name: "origin", + rules: []string{ + "||accounts.ea.com^", + "||origin.com^", + "||signin.ea.com^", + }, }, { name: "steam", rules: []string{ @@ -160,6 +194,7 @@ var serviceRulesArray = []svc{{ "||amazon.com.br^", "||amazon.co.jp^", "||amazon.com.mx^", + "||amazon.com.tr^", "||amazon.co.uk^", "||createspace.com^", "||aws", @@ -209,47 +244,81 @@ var serviceRulesArray = []svc{{ "||toutiaocloud.net^", "||bdurl.com^", "||bytecdn.cn^", + "||bytedapm.com^", "||byteimg.com^", + "||byteoversea.com^", "||ixigua.com^", "||muscdn.com^", "||bytedance.map.fastly.net^", "||douyin.com^", "||tiktokv.com^", + "||toutiaovod.com^", + "||douyincdn.com^", }, }, { - name: "vimeo", - rules: []string{"||vimeo.com^", "||vimeocdn.com^", "*vod-adaptive.akamaized.net^"}, + name: "vimeo", + rules: []string{ + "*vod-adaptive.akamaized.net^", + "||vimeo.com^", + "||vimeocdn.com^", + }, }, { - name: "pinterest", - rules: []string{"||pinterest.*^", "||pinimg.com^"}, + name: "pinterest", + rules: []string{ + "||pinimg.com^", + "||pinterest.*^", + }, }, { name: "imgur", rules: []string{"||imgur.com^"}, }, { - name: "dailymotion", - rules: []string{"||dailymotion.com^", "||dm-event.net^", "||dmcdn.net^"}, + name: "dailymotion", + rules: []string{ + "||dailymotion.com^", + "||dm-event.net^", + "||dmcdn.net^", + }, }, { name: "qq", rules: []string{ // Block qq.com and subdomains excluding WeChat's domains. "||qq.com^$denyallow=wx.qq.com|weixin.qq.com", "||qqzaixian.com^", + "||qq-video.cdn-go.cn^", + "||url.cn^", }, }, { - name: "wechat", - rules: []string{"||wechat.com^", "||weixin.qq.com^", "||wx.qq.com^"}, + name: "wechat", + rules: []string{ + "||wechat.com^", + "||weixin.qq.com.cn^", + "||weixin.qq.com^", + "||weixinbridge.com^", + "||wx.qq.com^", + }, }, { name: "viber", rules: []string{"||viber.com^"}, }, { - name: "weibo", - rules: []string{"||weibo.com^"}, + name: "weibo", + rules: []string{ + "||weibo.cn^", + "||weibo.com^", + "||weibocdn.com^", + }, }, { - name: "9gag", - rules: []string{"||9cache.com^", "||9gag.com^"}, + name: "9gag", + rules: []string{ + "||9cache.com^", + "||9gag.com^", + }, }, { - name: "telegram", - rules: []string{"||t.me^", "||telegram.me^", "||telegram.org^"}, + name: "telegram", + rules: []string{ + "||t.me^", + "||telegram.me^", + "||telegram.org^", + }, }, { name: "disneyplus", rules: []string{ @@ -283,6 +352,17 @@ var serviceRulesArray = []svc{{ "||tinder.com^", "||tindersparks.com^", }, +}, { + name: "bilibili", + rules: []string{ + "||biliapi.net^", + "||bilibili.com^", + "||biligame.com^", + "||bilivideo.cn^", + "||bilivideo.com^", + "||dreamcast.hk^", + "||hdslb.com^", + }, }} // convert array to map diff --git a/internal/filtering/dnsrewrite_test.go b/internal/filtering/dnsrewrite_test.go index 877b46a8..f8415fbf 100644 --- a/internal/filtering/dnsrewrite_test.go +++ b/internal/filtering/dnsrewrite_test.go @@ -61,22 +61,22 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { testCasesA := []struct { name string - want []interface{} + want []any rcode int dtyp uint16 }{{ name: "a-record", rcode: dns.RcodeSuccess, - want: []interface{}{ipv4p1}, + want: []any{ipv4p1}, dtyp: dns.TypeA, }, { name: "aaaa-record", - want: []interface{}{ipv6p1}, + want: []any{ipv6p1}, rcode: dns.RcodeSuccess, dtyp: dns.TypeAAAA, }, { name: "txt-record", - want: []interface{}{"hello-world"}, + want: []any{"hello-world"}, rcode: dns.RcodeSuccess, dtyp: dns.TypeTXT, }, { @@ -86,22 +86,22 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { dtyp: 0, }, { name: "a-records", - want: []interface{}{ipv4p1, ipv4p2}, + want: []any{ipv4p1, ipv4p2}, rcode: dns.RcodeSuccess, dtyp: dns.TypeA, }, { name: "aaaa-records", - want: []interface{}{ipv6p1, ipv6p2}, + want: []any{ipv6p1, ipv6p2}, rcode: dns.RcodeSuccess, dtyp: dns.TypeAAAA, }, { name: "disable-one", - want: []interface{}{ipv4p2}, + want: []any{ipv4p2}, rcode: dns.RcodeSuccess, dtyp: dns.TypeA, }, { name: "disable-cname", - want: []interface{}{ipv4p1}, + want: []any{ipv4p1}, rcode: dns.RcodeSuccess, dtyp: dns.TypeA, }} diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go index 8af49b0c..4a3e6b28 100644 --- a/internal/filtering/filtering.go +++ b/internal/filtering/filtering.go @@ -6,7 +6,6 @@ import ( "fmt" "io/fs" "net" - "net/http" "os" "runtime" "runtime/debug" @@ -14,6 +13,7 @@ import ( "sync" "sync/atomic" + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/cache" @@ -94,7 +94,7 @@ type Config struct { ConfigModified func() `yaml:"-"` // Register an HTTP handler - HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"` + HTTPRegister aghhttp.RegisterFunc `yaml:"-"` // CustomResolver is the resolver used by DNSFilter. CustomResolver Resolver `yaml:"-"` diff --git a/internal/filtering/filtering_test.go b/internal/filtering/filtering_test.go index 79c4d040..95554b07 100644 --- a/internal/filtering/filtering_test.go +++ b/internal/filtering/filtering_test.go @@ -21,6 +21,11 @@ func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } +const ( + sbBlocked = "wmconvirus.narod.ru" + pcBlocked = "pornhub.com" +) + var setts = Settings{ ProtectionEnabled: true, } @@ -173,43 +178,37 @@ func TestSafeBrowsing(t *testing.T) { d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) - const matching = "wmconvirus.narod.ru" - d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ - Hostname: matching, - Block: true, - }) - d.checkMatch(t, matching) - require.Contains(t, logOutput.String(), "SafeBrowsing lookup for "+matching) + d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true)) + d.checkMatch(t, sbBlocked) - d.checkMatch(t, "test."+matching) + require.Contains(t, logOutput.String(), fmt.Sprintf("safebrowsing lookup for %q", sbBlocked)) + + d.checkMatch(t, "test."+sbBlocked) d.checkMatchEmpty(t, "yandex.ru") - d.checkMatchEmpty(t, "pornhub.com") + d.checkMatchEmpty(t, pcBlocked) // Cached result. d.safeBrowsingServer = "127.0.0.1" - d.checkMatch(t, matching) - d.checkMatchEmpty(t, "pornhub.com") + d.checkMatch(t, sbBlocked) + d.checkMatchEmpty(t, pcBlocked) d.safeBrowsingServer = defaultSafebrowsingServer } func TestParallelSB(t *testing.T) { d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) - const matching = "wmconvirus.narod.ru" - d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ - Hostname: matching, - Block: true, - }) + + d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true)) t.Run("group", func(t *testing.T) { for i := 0; i < 100; i++ { t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) { t.Parallel() - d.checkMatch(t, matching) - d.checkMatch(t, "test."+matching) + d.checkMatch(t, sbBlocked) + d.checkMatch(t, "test."+sbBlocked) d.checkMatchEmpty(t, "yandex.ru") - d.checkMatchEmpty(t, "pornhub.com") + d.checkMatchEmpty(t, pcBlocked) }) } }) @@ -382,23 +381,19 @@ func TestParentalControl(t *testing.T) { d := newForTest(t, &Config{ParentalEnabled: true}, nil) t.Cleanup(d.Close) - const matching = "pornhub.com" - d.SetParentalUpstream(&aghtest.TestBlockUpstream{ - Hostname: matching, - Block: true, - }) - d.checkMatch(t, matching) - require.Contains(t, logOutput.String(), "Parental lookup for "+matching) + d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true)) + d.checkMatch(t, pcBlocked) + require.Contains(t, logOutput.String(), fmt.Sprintf("parental lookup for %q", pcBlocked)) - d.checkMatch(t, "www."+matching) + d.checkMatch(t, "www."+pcBlocked) d.checkMatchEmpty(t, "www.yandex.ru") d.checkMatchEmpty(t, "yandex.ru") d.checkMatchEmpty(t, "api.jquery.com") // Test cached result. d.parentalServer = "127.0.0.1" - d.checkMatch(t, matching) + d.checkMatch(t, pcBlocked) d.checkMatchEmpty(t, "yandex.ru") } @@ -445,7 +440,7 @@ func TestMatching(t *testing.T) { }, { name: "sanity", rules: "||doubleclick.net^", - host: "wmconvirus.narod.ru", + host: sbBlocked, wantIsFiltered: false, wantReason: NotFilteredNotFound, wantDNSType: dns.TypeA, @@ -765,14 +760,9 @@ func TestClientSettings(t *testing.T) { }}, ) t.Cleanup(d.Close) - d.SetParentalUpstream(&aghtest.TestBlockUpstream{ - Hostname: "pornhub.com", - Block: true, - }) - d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ - Hostname: "wmconvirus.narod.ru", - Block: true, - }) + + d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true)) + d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true)) type testCase struct { name string @@ -787,12 +777,12 @@ func TestClientSettings(t *testing.T) { wantReason: FilteredBlockList, }, { name: "parental", - host: "pornhub.com", + host: pcBlocked, before: true, wantReason: FilteredParental, }, { name: "safebrowsing", - host: "wmconvirus.narod.ru", + host: sbBlocked, before: false, wantReason: FilteredSafeBrowsing, }, { @@ -836,33 +826,29 @@ func TestClientSettings(t *testing.T) { func BenchmarkSafeBrowsing(b *testing.B) { d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil) b.Cleanup(d.Close) - blocked := "wmconvirus.narod.ru" - d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ - Hostname: blocked, - Block: true, - }) + + d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true)) + for n := 0; n < b.N; n++ { - res, err := d.CheckHost(blocked, dns.TypeA, &setts) + res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts) require.NoError(b, err) - assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked) + assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked) } } func BenchmarkSafeBrowsingParallel(b *testing.B) { d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil) b.Cleanup(d.Close) - blocked := "wmconvirus.narod.ru" - d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{ - Hostname: blocked, - Block: true, - }) + + d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true)) + b.RunParallel(func(pb *testing.PB) { for pb.Next() { - res, err := d.CheckHost(blocked, dns.TypeA, &setts) + res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts) require.NoError(b, err) - assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked) + assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked) } }) } diff --git a/internal/filtering/safebrowsing.go b/internal/filtering/safebrowsing.go index 9de4bcd2..e49c4070 100644 --- a/internal/filtering/safebrowsing.go +++ b/internal/filtering/safebrowsing.go @@ -24,10 +24,11 @@ import ( // Safe browsing and parental control methods. +// TODO(a.garipov): Make configurable. const ( dnsTimeout = 3 * time.Second - defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query` - defaultParentalServer = `https://dns-family.adguard.com/dns-query` + defaultSafebrowsingServer = `https://family.adguard-dns.com/dns-query` + defaultParentalServer = `https://family.adguard-dns.com/dns-query` sbTXTSuffix = `sb.dns.adguard.com.` pcTXTSuffix = `pc.dns.adguard.com.` ) @@ -313,7 +314,7 @@ func (d *DNSFilter) checkSafeBrowsing( if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() - defer timer.LogElapsed("SafeBrowsing lookup for %s", host) + defer timer.LogElapsed("safebrowsing lookup for %q", host) } sctx := &sbCtx{ @@ -347,7 +348,7 @@ func (d *DNSFilter) checkParental( if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() - defer timer.LogElapsed("Parental lookup for %s", host) + defer timer.LogElapsed("parental lookup for %q", host) } sctx := &sbCtx{ diff --git a/internal/filtering/safebrowsing_test.go b/internal/filtering/safebrowsing_test.go index 2dec3668..f2cc846c 100644 --- a/internal/filtering/safebrowsing_test.go +++ b/internal/filtering/safebrowsing_test.go @@ -74,21 +74,20 @@ func TestSafeBrowsingCache(t *testing.T) { c.hashToHost[hash] = "sub.host.com" assert.Equal(t, -1, c.getCached()) - // match "sub.host.com" from cache, - // but another hash for "nonexisting.com" is not in cache - // which means that we must get data from server for it + // Match "sub.host.com" from cache. Another hash for "host.example" is not + // in the cache, so get data for it from the server. c.hashToHost = make(map[[32]byte]string) hash = sha256.Sum256([]byte("sub.host.com")) c.hashToHost[hash] = "sub.host.com" - hash = sha256.Sum256([]byte("nonexisting.com")) - c.hashToHost[hash] = "nonexisting.com" + hash = sha256.Sum256([]byte("host.example")) + c.hashToHost[hash] = "host.example" assert.Empty(t, c.getCached()) hash = sha256.Sum256([]byte("sub.host.com")) _, ok := c.hashToHost[hash] assert.False(t, ok) - hash = sha256.Sum256([]byte("nonexisting.com")) + hash = sha256.Sum256([]byte("host.example")) _, ok = c.hashToHost[hash] assert.True(t, ok) @@ -111,8 +110,7 @@ func TestSBPC_checkErrorUpstream(t *testing.T) { d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil) t.Cleanup(d.Close) - ups := &aghtest.TestErrUpstream{} - + ups := aghtest.NewErrorUpstream() d.SetSafeBrowsingUpstream(ups) d.SetParentalUpstream(ups) @@ -170,10 +168,16 @@ func TestSBPC(t *testing.T) { for _, tc := range testCases { // Prepare the upstream. - ups := &aghtest.TestBlockUpstream{ - Hostname: hostname, - Block: tc.block, + ups := aghtest.NewBlockUpstream(hostname, tc.block) + + var numReq int + onExchange := ups.OnExchange + ups.OnExchange = func(req *dns.Msg) (resp *dns.Msg, err error) { + numReq++ + + return onExchange(req) } + d.SetSafeBrowsingUpstream(ups) d.SetParentalUpstream(ups) @@ -196,7 +200,7 @@ func TestSBPC(t *testing.T) { assert.Equal(t, hits, tc.testCache.Stats().Hit) // There was one request to an upstream. - assert.Equal(t, 1, ups.RequestsCount()) + assert.Equal(t, 1, numReq) // Now make the same request to check the cache was used. res, err = tc.testFunc(hostname, dns.TypeA, setts) @@ -214,7 +218,7 @@ func TestSBPC(t *testing.T) { assert.Equal(t, hits+1, tc.testCache.Stats().Hit) // Check that there were no additional requests. - assert.Equal(t, 1, ups.RequestsCount()) + assert.Equal(t, 1, numReq) }) purgeCaches(d) diff --git a/internal/home/clients.go b/internal/home/clients.go index d4d6b959..e50b7904 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -2,9 +2,11 @@ package home import ( "bytes" + "encoding" "fmt" "net" "sort" + "strings" "sync" "time" @@ -59,6 +61,33 @@ const ( ClientSourceHostsFile ) +var _ fmt.Stringer = clientSource(0) + +// String returns a human-readable name of cs. +func (cs clientSource) String() (s string) { + switch cs { + case ClientSourceWHOIS: + return "WHOIS" + case ClientSourceARP: + return "ARP" + case ClientSourceRDNS: + return "rDNS" + case ClientSourceDHCP: + return "DHCP" + case ClientSourceHostsFile: + return "etc/hosts" + default: + return "" + } +} + +var _ encoding.TextMarshaler = clientSource(0) + +// MarshalText implements encoding.TextMarshaler for the clientSource. +func (cs clientSource) MarshalText() (text []byte, err error) { + return []byte(cs.String()), nil +} + // clientSourceConf is used to configure where the runtime clients will be // obtained from. type clientSourcesConf struct { @@ -396,6 +425,7 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) { c.Tags = stringutil.CloneSlice(c.Tags) c.BlockedServices = stringutil.CloneSlice(c.BlockedServices) c.Upstreams = stringutil.CloneSlice(c.Upstreams) + return c, true } @@ -492,7 +522,7 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) { // findRuntimeClientLocked finds a runtime client by their IP address. For // internal use only. func (clients *clientsContainer) findRuntimeClientLocked(ip net.IP) (rc *RuntimeClient, ok bool) { - var v interface{} + var v any v, ok = clients.ipToRC.Get(ip) if !ok { return nil, false @@ -546,7 +576,7 @@ func (clients *clientsContainer) check(c *Client) (err error) { } else if mac, err = net.ParseMAC(id); err == nil { c.IDs[i] = mac.String() } else if err = dnsforward.ValidateClientID(id); err == nil { - c.IDs[i] = id + c.IDs[i] = strings.ToLower(id) } else { return fmt.Errorf("invalid clientid at index %d: %q", i, id) } @@ -742,8 +772,7 @@ func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSourc // addHostLocked adds a new IP-hostname pairing. For internal use only. func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clientSource) (ok bool) { - var rc *RuntimeClient - rc, ok = clients.findRuntimeClientLocked(ip) + rc, ok := clients.findRuntimeClientLocked(ip) if ok { if rc.Source > src { return false @@ -769,7 +798,7 @@ func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clien // rmHostsBySrc removes all entries that match the specified source. func (clients *clientsContainer) rmHostsBySrc(src clientSource) { n := 0 - clients.ipToRC.Range(func(ip net.IP, v interface{}) (cont bool) { + clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) { rc, ok := v.(*RuntimeClient) if !ok { log.Error("clients: bad type %T in ipToRC for %s", v, ip) @@ -797,26 +826,21 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) { clients.rmHostsBySrc(ClientSourceHostsFile) n := 0 - hosts.Range(func(ip net.IP, v interface{}) (cont bool) { - hosts, ok := v.(*stringutil.Set) + hosts.Range(func(ip net.IP, v any) (cont bool) { + rec, ok := v.(*aghnet.HostsRecord) if !ok { log.Error("dns: bad type %T in ipToRC for %s", v, ip) return true } - hosts.Range(func(name string) (cont bool) { - if clients.addHostLocked(ip, name, ClientSourceHostsFile) { - n++ - } - - return true - }) + clients.addHostLocked(ip, rec.Canonical, ClientSourceHostsFile) + n++ return true }) - log.Debug("clients: added %d client aliases from system hosts-file", n) + log.Debug("clients: added %d client aliases from system hosts file", n) } // addFromSystemARP adds the IP-hostname pairings from the output of the arp -a diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index 1f053343..5f10ccbe 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -47,9 +47,9 @@ type clientJSON struct { type runtimeClientJSON struct { WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"` - Name string `json:"name"` - Source string `json:"source"` - IP net.IP `json:"ip"` + Name string `json:"name"` + Source clientSource `json:"source"` + IP net.IP `json:"ip"` } type clientListJSON struct { @@ -70,7 +70,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http data.Clients = append(data.Clients, cj) } - clients.ipToRC.Range(func(ip net.IP, v interface{}) (cont bool) { + clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) { rc, ok := v.(*RuntimeClient) if !ok { log.Error("dns: bad type %T in ipToRC for %s", v, ip) @@ -81,20 +81,9 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http cj := runtimeClientJSON{ WHOISInfo: rc.WHOISInfo, - Name: rc.Host, - IP: ip, - } - - cj.Source = "etc/hosts" - switch rc.Source { - case ClientSourceDHCP: - cj.Source = "DHCP" - case ClientSourceRDNS: - cj.Source = "rDNS" - case ClientSourceARP: - cj.Source = "ARP" - case ClientSourceWHOIS: - cj.Source = "WHOIS" + Name: rc.Host, + Source: rc.Source, + IP: ip, } data.RuntimeClients = append(data.RuntimeClients, cj) @@ -107,13 +96,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http w.Header().Set("Content-Type", "application/json") e := json.NewEncoder(w).Encode(data) if e != nil { - aghhttp.Error( - r, - w, - http.StatusInternalServerError, - "Failed to encode to json: %v", - e, - ) + aghhttp.Error(r, w, http.StatusInternalServerError, "failed to encode to json: %v", e) return } @@ -279,9 +262,9 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj *clientJSON) { rc, ok := clients.FindRuntimeClient(ip) if !ok { - // It is still possible that the IP used to be in the runtime - // clients list, but then the server was reloaded. So, check - // the DNS server's blocked IP list. + // It is still possible that the IP used to be in the runtime clients + // list, but then the server was reloaded. So, check the DNS server's + // blocked IP list. // // See https://github.com/AdguardTeam/AdGuardHome/issues/2428. disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr) diff --git a/internal/home/config.go b/internal/home/config.go index 14f5781e..5bd9b98b 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -1,6 +1,7 @@ package home import ( + "bytes" "fmt" "net" "os" @@ -19,7 +20,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/timeutil" "github.com/google/renameio/maybe" - yaml "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v3" ) const ( @@ -27,15 +28,36 @@ const ( filterDir = "filters" // cache location for downloaded filters, it's under DataDir ) -// logSettings +// logSettings are the logging settings part of the configuration file. +// +// TODO(a.garipov): Put them into a separate object. type logSettings struct { - LogCompress bool `yaml:"log_compress"` // Compress determines if the rotated log files should be compressed using gzip (default: false) - LogLocalTime bool `yaml:"log_localtime"` // If the time used for formatting the timestamps in is the computer's local time (default: false [UTC]) - LogMaxBackups int `yaml:"log_max_backups"` // Maximum number of old log files to retain (MaxAge may still cause them to get deleted) - LogMaxSize int `yaml:"log_max_size"` // Maximum size in megabytes of the log file before it gets rotated (default 100 MB) - LogMaxAge int `yaml:"log_max_age"` // MaxAge is the maximum number of days to retain old log files - LogFile string `yaml:"log_file"` // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog - Verbose bool `yaml:"verbose"` // If true, verbose logging is enabled + // File is the path to the log file. If empty, logs are written to stdout. + // If "syslog", logs are written to syslog. + File string `yaml:"log_file"` + + // MaxBackups is the maximum number of old log files to retain. + // + // NOTE: MaxAge may still cause them to get deleted. + MaxBackups int `yaml:"log_max_backups"` + + // MaxSize is the maximum size of the log file before it gets rotated, in + // megabytes. The default value is 100 MB. + MaxSize int `yaml:"log_max_size"` + + // MaxAge is the maximum duration for retaining old log files, in days. + MaxAge int `yaml:"log_max_age"` + + // Compress determines, if the rotated log files should be compressed using + // gzip. + Compress bool `yaml:"log_compress"` + + // LocalTime determines, if the time used for formatting the timestamps in + // is the computer's local time. + LocalTime bool `yaml:"log_localtime"` + + // Verbose determines, if verbose (aka debug) logging is enabled. + Verbose bool `yaml:"verbose"` } // osConfig contains OS-related configuration. @@ -223,11 +245,11 @@ var config = &configuration{ }, }, logSettings: logSettings{ - LogCompress: false, - LogLocalTime: false, - LogMaxBackups: 0, - LogMaxSize: 100, - LogMaxAge: 3, + Compress: false, + LocalTime: false, + MaxBackups: 0, + MaxSize: 100, + MaxAge: 3, }, OSConfig: &osConfig{}, SchemaVersion: currentSchemaVersion, @@ -302,27 +324,28 @@ func parseConfig() (err error) { return err } - uc := aghalg.UniqChecker{} - addPorts( - uc, - tcpPort(config.BindPort), - tcpPort(config.BetaBindPort), - udpPort(config.DNS.Port), - ) + tcpPorts := aghalg.UniqChecker[tcpPort]{} + addPorts(tcpPorts, tcpPort(config.BindPort), tcpPort(config.BetaBindPort)) + + udpPorts := aghalg.UniqChecker[udpPort]{} + addPorts(udpPorts, udpPort(config.DNS.Port)) if config.TLS.Enabled { addPorts( - uc, - // TODO(e.burkov): Consider adding a udpPort with the same value if - // we ever support the HTTP/3 for web admin interface. + tcpPorts, tcpPort(config.TLS.PortHTTPS), tcpPort(config.TLS.PortDNSOverTLS), - udpPort(config.TLS.PortDNSOverQUIC), tcpPort(config.TLS.PortDNSCrypt), ) + + // TODO(e.burkov): Consider adding a udpPort with the same value when + // we add support for HTTP/3 for web admin interface. + addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC)) } - if err = uc.Validate(aghalg.IntIsBefore); err != nil { - return fmt.Errorf("validating ports: %w", err) + if err = tcpPorts.Validate(); err != nil { + return fmt.Errorf("validating tcp ports: %w", err) + } else if err = udpPorts.Validate(); err != nil { + return fmt.Errorf("validating udp ports: %w", err) } if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) { @@ -342,23 +365,11 @@ type udpPort int // tcpPort is the port number for TCP protocol. type tcpPort int -// addPorts is a helper for ports validation. It skips zero ports. Each of -// ports should be either a udpPort or a tcpPort. -func addPorts(uc aghalg.UniqChecker, ports ...interface{}) { +// addPorts is a helper for ports validation that skips zero ports. +func addPorts[T tcpPort | udpPort](uc aghalg.UniqChecker[T], ports ...T) { for _, p := range ports { - // Use separate cases for tcpPort and udpPort so that the untyped - // constant zero is converted to the appropriate type. - switch p := p.(type) { - case tcpPort: - if p != 0 { - uc.Add(p) - } - case udpPort: - if p != 0 { - uc.Add(p) - } - default: - // Go on. + if p != 0 { + uc.Add(p) } } } @@ -377,13 +388,14 @@ func readConfigFile() (fileData []byte, err error) { } // Saves configuration to the YAML file and also saves the user filter contents to a file -func (c *configuration) write() error { +func (c *configuration) write() (err error) { c.Lock() defer c.Unlock() if Context.auth != nil { config.Users = Context.auth.GetUsers() } + if Context.tls != nil { tlsConf := tlsConfigSettings{} Context.tls.WriteDiskConfig(&tlsConf) @@ -429,19 +441,20 @@ func (c *configuration) write() error { config.Clients.Persistent = Context.clients.forConfig() configFile := config.getConfigFilename() - log.Debug("Writing YAML file: %s", configFile) - yamlText, err := yaml.Marshal(&config) - if err != nil { - log.Error("Couldn't generate YAML file: %s", err) + log.Debug("writing config file %q", configFile) - return err + buf := &bytes.Buffer{} + enc := yaml.NewEncoder(buf) + enc.SetIndent(2) + + err = enc.Encode(config) + if err != nil { + return fmt.Errorf("generating config file: %w", err) } - err = maybe.WriteFile(configFile, yamlText, 0o644) + err = maybe.WriteFile(configFile, buf.Bytes(), 0o644) if err != nil { - log.Error("Couldn't save YAML config: %s", err) - - return err + return fmt.Errorf("writing config file: %w", err) } return nil diff --git a/internal/home/control.go b/internal/home/control.go index 8234e00e..54d1652a 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -189,7 +189,7 @@ func registerControlHandlers() { RegisterAuthHandlers() } -func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) { +func httpRegister(method, url string, handler http.HandlerFunc) { if method == "" { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method Context.mux.HandleFunc(url, postInstall(handler)) diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index 639403d8..a4c8651a 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -13,6 +13,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/miekg/dns" ) @@ -57,8 +58,8 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request err = validateFilterURL(fj.URL) if err != nil { - msg := fmt.Sprintf("invalid url: %s", err) - http.Error(w, msg, http.StatusBadRequest) + err = fmt.Errorf("invalid url: %s", err) + aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) return } @@ -178,16 +179,16 @@ func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ } } -type filterURLJSON struct { +type filterURLReqData struct { Name string `json:"name"` URL string `json:"url"` Enabled bool `json:"enabled"` } type filterURLReq struct { - URL string `json:"url"` - Whitelist bool `json:"whitelist"` - Data filterURLJSON `json:"data"` + Data *filterURLReqData `json:"data"` + URL string `json:"url"` + Whitelist bool `json:"whitelist"` } func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request) { @@ -199,10 +200,17 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request return } + if fj.Data == nil { + err = errors.Error("data cannot be null") + aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) + + return + } + err = validateFilterURL(fj.Data.URL) if err != nil { - msg := fmt.Sprintf("invalid url: %s", err) - http.Error(w, msg, http.StatusBadRequest) + err = fmt.Errorf("invalid url: %s", err) + aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) return } @@ -223,11 +231,8 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request } onConfigModified() - restart := false - if (status & statusEnabledChanged) != 0 { - // we must add or remove filter rules - restart = true - } + + restart := (status & statusEnabledChanged) != 0 if (status&statusUpdateRequired) != 0 && fj.Data.Enabled { // download new filter and apply its rules flags := filterRefreshBlocklists @@ -242,6 +247,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request restart = true } } + if restart { enableFilters(true) } @@ -311,20 +317,20 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques } type filterJSON struct { - ID int64 `json:"id"` - Enabled bool `json:"enabled"` URL string `json:"url"` Name string `json:"name"` + LastUpdated string `json:"last_updated,omitempty"` + ID int64 `json:"id"` RulesCount uint32 `json:"rules_count"` - LastUpdated string `json:"last_updated"` + Enabled bool `json:"enabled"` } type filteringConfig struct { - Enabled bool `json:"enabled"` - Interval uint32 `json:"interval"` // in hours Filters []filterJSON `json:"filters"` WhitelistFilters []filterJSON `json:"whitelist_filters"` UserRules []string `json:"user_rules"` + Interval uint32 `json:"interval"` // in hours + Enabled bool `json:"enabled"` } func filterToJSON(f filter) filterJSON { @@ -402,16 +408,12 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request } type checkHostRespRule struct { - FilterListID int64 `json:"filter_list_id"` Text string `json:"text"` + FilterListID int64 `json:"filter_list_id"` } type checkHostResp struct { Reason string `json:"reason"` - // FilterID is the ID of the rule's filter list. - // - // Deprecated: Use Rules[*].FilterListID. - FilterID int64 `json:"filter_id"` // Rule is the text of the matched rule. // @@ -426,6 +428,11 @@ type checkHostResp struct { // for Rewrite: CanonName string `json:"cname"` // CNAME value IPList []net.IP `json:"ip_addrs"` // list of IP addresses + + // FilterID is the ID of the rule's filter list. + // + // Deprecated: Use Rules[*].FilterListID. + FilterID int64 `json:"filter_id"` } func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) { diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 0435651b..c46f3459 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -105,19 +105,22 @@ type checkConfResp struct { // validateWeb returns error is the web part if the initial configuration can't // be set. -func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) { +func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err error) { defer func() { err = errors.Annotate(err, "validating ports: %w") }() - port := req.Web.Port - addPorts(uc, tcpPort(config.BetaBindPort), tcpPort(port)) - if err = uc.Validate(aghalg.IntIsBefore); err != nil { - // Avoid duplicating the error into the status of DNS. - uc[port] = 1 + portInt := req.Web.Port + port := tcpPort(portInt) + addPorts(tcpPorts, tcpPort(config.BetaBindPort), port) + if err = tcpPorts.Validate(); err != nil { + // Reset the value for the port to 1 to make sure that validateDNS + // doesn't throw the same error, unless the same TCP port is set there + // as well. + tcpPorts[port] = 1 return err } - switch port { + switch portInt { case 0, config.BindPort: return nil default: @@ -125,21 +128,18 @@ func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) { // unbound after install. } - return aghnet.CheckPort("tcp", req.Web.IP, port) + return aghnet.CheckPort("tcp", req.Web.IP, portInt) } // validateDNS returns error if the DNS part of the initial configuration can't // be set. canAutofix is true if the port can be unbound by AdGuard Home // automatically. -func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, err error) { +func (req *checkConfReq) validateDNS( + tcpPorts aghalg.UniqChecker[tcpPort], +) (canAutofix bool, err error) { defer func() { err = errors.Annotate(err, "validating ports: %w") }() port := req.DNS.Port - addPorts(uc, udpPort(port)) - if err = uc.Validate(aghalg.IntIsBefore); err != nil { - return false, err - } - switch port { case 0: return false, nil @@ -148,6 +148,11 @@ func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, er // by AdGuard Home for web interface. default: // Check TCP as well. + addPorts(tcpPorts, tcpPort(port)) + if err = tcpPorts.Validate(); err != nil { + return false, err + } + err = aghnet.CheckPort("tcp", req.DNS.IP, port) if err != nil { return false, err @@ -185,13 +190,12 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) } resp := &checkConfResp{} - uc := aghalg.UniqChecker{} - - if err = req.validateWeb(uc); err != nil { + tcpPorts := aghalg.UniqChecker[tcpPort]{} + if err = req.validateWeb(tcpPorts); err != nil { resp.Web.Status = err.Error() } - if resp.DNS.CanAutofix, err = req.validateDNS(uc); err != nil { + if resp.DNS.CanAutofix, err = req.validateDNS(tcpPorts); err != nil { resp.DNS.Status = err.Error() } else if !req.DNS.IP.IsUnspecified() { resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP) @@ -212,7 +216,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) func handleStaticIP(ip net.IP, set bool) staticIPJSON { resp := staticIPJSON{} - interfaceName := aghnet.GetInterfaceByIP(ip) + interfaceName := aghnet.InterfaceByIP(ip) resp.Static = "no" if len(interfaceName) == 0 { diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index ae469598..91164696 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -7,11 +7,11 @@ import ( "net/http" "os" "os/exec" - "path/filepath" "runtime" "syscall" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/updater" @@ -117,7 +117,18 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) { return } - err := Context.updater.Update() + // Retain the current absolute path of the executable, since the updater is + // likely to change the position current one to the backup directory. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/4735. + execPath, err := os.Executable() + if err != nil { + aghhttp.Error(r, w, http.StatusInternalServerError, "getting path: %s", err) + + return + } + + err = Context.updater.Update() if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) @@ -129,13 +140,10 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) { f.Flush() } - // The background context is used because the underlying functions wrap - // it with timeout and shut down the server, which handles current - // request. It also should be done in a separate goroutine due to the - // same reason. - go func() { - finishUpdate(context.Background()) - }() + // The background context is used because the underlying functions wrap it + // with timeout and shut down the server, which handles current request. It + // also should be done in a separate goroutine for the same reason. + go finishUpdate(context.Background(), execPath) } // versionResponse is the response for /control/version.json endpoint. @@ -147,8 +155,8 @@ type versionResponse struct { // setAllowedToAutoUpdate sets CanAutoUpdate to true if AdGuard Home is actually // allowed to perform an automatic update by the OS. func (vr *versionResponse) setAllowedToAutoUpdate() (err error) { - if vr.CanAutoUpdate == nil || !*vr.CanAutoUpdate { - return + if vr.CanAutoUpdate != aghalg.NBTrue { + return nil } tlsConf := &tlsConfigSettings{} @@ -162,7 +170,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) { } } - vr.CanAutoUpdate = &canUpdate + vr.CanAutoUpdate = aghalg.BoolToNullBool(canUpdate) return nil } @@ -174,46 +182,46 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) { } // finishUpdate completes an update procedure. -func finishUpdate(ctx context.Context) { - log.Info("Stopping all tasks") +func finishUpdate(ctx context.Context, execPath string) { + var err error + + log.Info("stopping all tasks") + cleanup(ctx) cleanupAlways() - exeName := "AdGuardHome" - if runtime.GOOS == "windows" { - exeName = "AdGuardHome.exe" - } - curBinName := filepath.Join(Context.workDir, exeName) - if runtime.GOOS == "windows" { if Context.runningAsService { - // Note: - // we can't restart the service via "kardianos/service" package - it kills the process first - // we can't start a new instance - Windows doesn't allow it + // NOTE: We can't restart the service via "kardianos/service" + // package, because it kills the process first we can't start a new + // instance, because Windows doesn't allow it. + // + // TODO(a.garipov): Recheck the claim above. cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome") - err := cmd.Start() + err = cmd.Start() if err != nil { - log.Fatalf("exec.Command() failed: %s", err) + log.Fatalf("restarting: stopping: %s", err) } + os.Exit(0) } - cmd := exec.Command(curBinName, os.Args[1:]...) - log.Info("Restarting: %v", cmd.Args) + cmd := exec.Command(execPath, os.Args[1:]...) + log.Info("restarting: %q %q", execPath, os.Args[1:]) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Start() + err = cmd.Start() if err != nil { - log.Fatalf("exec.Command() failed: %s", err) + log.Fatalf("restarting:: %s", err) } + os.Exit(0) - } else { - log.Info("Restarting: %v", os.Args) - err := syscall.Exec(curBinName, os.Args, os.Environ()) - if err != nil { - log.Fatalf("syscall.Exec() failed: %s", err) - } - // Unreachable code + } + + log.Info("restarting: %q %q", execPath, os.Args[1:]) + err = syscall.Exec(execPath, os.Args, os.Environ()) + if err != nil { + log.Fatalf("restarting: %s", err) } } diff --git a/internal/home/dns.go b/internal/home/dns.go index 1c04c6c3..ab5b0330 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -17,7 +17,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/ameshkov/dnscrypt/v2" - yaml "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v3" ) // Default ports. @@ -58,6 +58,7 @@ func initDNSServer() (err error) { } conf := querylog.Config{ + Anonymizer: anonymizer, ConfigModified: onConfigModified, HTTPRegister: httpRegister, FindClient: Context.clients.findMultiple, @@ -67,7 +68,6 @@ func initDNSServer() (err error) { Enabled: config.DNS.QueryLogEnabled, FileEnabled: config.DNS.QueryLogFileEnabled, AnonymizeClientIP: config.DNS.AnonymizeClientIP, - Anonymizer: anonymizer, } Context.queryLog = querylog.New(conf) @@ -221,6 +221,10 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { newConf.TLSConfig = tlsConf.TLSConfig newConf.TLSConfig.ServerName = tlsConf.ServerName + if tlsConf.PortHTTPS != 0 { + newConf.HTTPSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortHTTPS) + } + if tlsConf.PortDNSOverTLS != 0 { newConf.TLSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortDNSOverTLS) } @@ -392,7 +396,7 @@ func startDNSServer() error { Context.queryLog.Start() const topClientsNumber = 100 // the number of clients to get - for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) { + for _, ip := range Context.stats.TopClientsIP(topClientsNumber) { if ip == nil { continue } @@ -451,7 +455,12 @@ func closeDNSServer() { } if Context.stats != nil { - Context.stats.Close() + err := Context.stats.Close() + if err != nil { + log.Debug("closing stats: %s", err) + } + + // TODO(e.burkov): Find out if it's safe. Context.stats = nil } diff --git a/internal/home/home.go b/internal/home/home.go index 539552d1..29cb6d5d 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -47,7 +47,7 @@ type homeContext struct { // -- clients clientsContainer // per-client-settings module - stats stats.Stats // statistics module + stats stats.Interface // statistics module queryLog querylog.QueryLog // query log module dnsServer *dnsforward.Server // DNS module rdns *RDNS // rDNS module @@ -298,24 +298,27 @@ func setupConfig(args options) (err error) { Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb) if args.bindPort != 0 { - uc := aghalg.UniqChecker{} - addPorts( - uc, - tcpPort(args.bindPort), - tcpPort(config.BetaBindPort), - udpPort(config.DNS.Port), - ) + tcpPorts := aghalg.UniqChecker[tcpPort]{} + addPorts(tcpPorts, tcpPort(args.bindPort), tcpPort(config.BetaBindPort)) + + udpPorts := aghalg.UniqChecker[udpPort]{} + addPorts(udpPorts, udpPort(config.DNS.Port)) + if config.TLS.Enabled { addPorts( - uc, + tcpPorts, tcpPort(config.TLS.PortHTTPS), tcpPort(config.TLS.PortDNSOverTLS), - udpPort(config.TLS.PortDNSOverQUIC), tcpPort(config.TLS.PortDNSCrypt), ) + + addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC)) } - if err = uc.Validate(aghalg.IntIsBefore); err != nil { - return fmt.Errorf("validating ports: %w", err) + + if err = tcpPorts.Validate(); err != nil { + return fmt.Errorf("validating tcp ports: %w", err) + } else if err = udpPorts.Validate(); err != nil { + return fmt.Errorf("validating udp ports: %w", err) } config.BindPort = args.bindPort @@ -599,17 +602,17 @@ func configureLogger(args options) { ls.Verbose = true } if args.logFile != "" { - ls.LogFile = args.logFile - } else if config.LogFile != "" { - ls.LogFile = config.LogFile + ls.File = args.logFile + } else if config.File != "" { + ls.File = config.File } // Handle default log settings overrides - ls.LogCompress = config.LogCompress - ls.LogLocalTime = config.LogLocalTime - ls.LogMaxBackups = config.LogMaxBackups - ls.LogMaxSize = config.LogMaxSize - ls.LogMaxAge = config.LogMaxAge + ls.Compress = config.Compress + ls.LocalTime = config.LocalTime + ls.MaxBackups = config.MaxBackups + ls.MaxSize = config.MaxSize + ls.MaxAge = config.MaxAge // log.SetLevel(log.INFO) - default if ls.Verbose { @@ -620,27 +623,27 @@ func configureLogger(args options) { // happen pretty quickly. log.SetFlags(log.LstdFlags | log.Lmicroseconds) - if args.runningAsService && ls.LogFile == "" && runtime.GOOS == "windows" { + if args.runningAsService && ls.File == "" && runtime.GOOS == "windows" { // When running as a Windows service, use eventlog by default if nothing // else is configured. Otherwise, we'll simply lose the log output. - ls.LogFile = configSyslog + ls.File = configSyslog } // logs are written to stdout (default) - if ls.LogFile == "" { + if ls.File == "" { return } - if ls.LogFile == configSyslog { + if ls.File == configSyslog { // Use syslog where it is possible and eventlog on Windows err := aghos.ConfigureSyslog(serviceName) if err != nil { log.Fatalf("cannot initialize syslog: %s", err) } } else { - logFilePath := filepath.Join(Context.workDir, ls.LogFile) - if filepath.IsAbs(ls.LogFile) { - logFilePath = ls.LogFile + logFilePath := filepath.Join(Context.workDir, ls.File) + if filepath.IsAbs(ls.File) { + logFilePath = ls.File } _, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) @@ -650,11 +653,11 @@ func configureLogger(args options) { log.SetOutput(&lumberjack.Logger{ Filename: logFilePath, - Compress: ls.LogCompress, // disabled by default - LocalTime: ls.LogLocalTime, - MaxBackups: ls.LogMaxBackups, - MaxSize: ls.LogMaxSize, // megabytes - MaxAge: ls.LogMaxAge, // days + Compress: ls.Compress, // disabled by default + LocalTime: ls.LocalTime, + MaxBackups: ls.MaxBackups, + MaxSize: ls.MaxSize, // megabytes + MaxAge: ls.MaxAge, // days }) } } diff --git a/internal/home/i18n.go b/internal/home/i18n.go index 51f6a2ac..d58dfcf3 100644 --- a/internal/home/i18n.go +++ b/internal/home/i18n.go @@ -13,6 +13,7 @@ import ( // TODO(a.garipov): Get rid of a global or generate from .twosky.json. var allowedLanguages = stringutil.NewSet( + "ar", "be", "bg", "cs", @@ -50,7 +51,7 @@ var allowedLanguages = stringutil.NewSet( "zh-tw", ) -func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) { +func handleI18nCurrentLanguage(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/plain") log.Printf("config.Language is %s", config.Language) _, err := fmt.Fprintf(w, "%s\n", config.Language) @@ -58,6 +59,7 @@ func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) { msg := fmt.Sprintf("Unable to write response json: %s", err) log.Println(msg) http.Error(w, msg, http.StatusInternalServerError) + return } } @@ -69,6 +71,7 @@ func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) { msg := fmt.Sprintf("failed to read request body: %s", err) log.Println(msg) http.Error(w, msg, http.StatusBadRequest) + return } diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go index 82d0dbf1..40094a6a 100644 --- a/internal/home/mobileconfig.go +++ b/internal/home/mobileconfig.go @@ -11,7 +11,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - uuid "github.com/satori/go.uuid" + "github.com/google/uuid" "howett.net/plist" ) @@ -47,9 +47,9 @@ type payloadContent struct { PayloadType string PayloadIdentifier string - PayloadUUID string PayloadDisplayName string PayloadDescription string + PayloadUUID uuid.UUID PayloadVersion int } @@ -63,18 +63,14 @@ const dnsSettingsPayloadType = "com.apple.dnsSettings.managed" type mobileConfig struct { PayloadDescription string PayloadDisplayName string - PayloadIdentifier string PayloadType string - PayloadUUID string PayloadContent []*payloadContent + PayloadIdentifier uuid.UUID + PayloadUUID uuid.UUID PayloadVersion int PayloadRemovalDisallowed bool } -func genUUIDv4() string { - return uuid.NewV4().String() -} - const ( dnsProtoHTTPS = "HTTPS" dnsProtoTLS = "TLS" @@ -104,23 +100,23 @@ func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) { return nil, fmt.Errorf("bad dns protocol %q", proto) } - payloadID := fmt.Sprintf("%s.%s", dnsSettingsPayloadType, genUUIDv4()) + payloadID := fmt.Sprintf("%s.%s", dnsSettingsPayloadType, uuid.New()) data := &mobileConfig{ - PayloadDescription: "Adds AdGuard Home to macOS Big Sur " + - "and iOS 14 or newer systems", + PayloadDescription: "Adds AdGuard Home to macOS Big Sur and iOS 14 or newer systems", PayloadDisplayName: dspName, - PayloadIdentifier: genUUIDv4(), PayloadType: "Configuration", - PayloadUUID: genUUIDv4(), PayloadContent: []*payloadContent{{ + DNSSettings: d, + PayloadType: dnsSettingsPayloadType, PayloadIdentifier: payloadID, - PayloadUUID: genUUIDv4(), PayloadDisplayName: dspName, PayloadDescription: "Configures device to use AdGuard Home", + PayloadUUID: uuid.New(), PayloadVersion: 1, - DNSSettings: d, }}, + PayloadIdentifier: uuid.New(), + PayloadUUID: uuid.New(), PayloadVersion: 1, PayloadRemovalDisallowed: false, } diff --git a/internal/home/rdns_test.go b/internal/home/rdns_test.go index 08f4f013..870d0f04 100644 --- a/internal/home/rdns_test.go +++ b/internal/home/rdns_test.go @@ -3,15 +3,16 @@ package home import ( "bytes" "encoding/binary" + "fmt" "net" "sync" "testing" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/cache" - "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" @@ -80,8 +81,10 @@ func TestRDNS_Begin(t *testing.T) { binary.BigEndian.PutUint64(ttl, uint64(time.Now().Add(100*time.Hour).Unix())) rdns := &RDNS{ - ipCache: ipCache, - exchanger: &rDNSExchanger{}, + ipCache: ipCache, + exchanger: &rDNSExchanger{ + ex: aghtest.NewErrorUpstream(), + }, clients: &clientsContainer{ list: map[string]*Client{}, idIndex: tc.cliIDIndex, @@ -108,16 +111,22 @@ func TestRDNS_Begin(t *testing.T) { // rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests. type rDNSExchanger struct { - ex aghtest.Exchanger + ex upstream.Upstream usePrivate bool } // Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger. func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) { + rev, err := netutil.IPToReversedAddr(ip) + if err != nil { + return "", fmt.Errorf("reversing ip: %w", err) + } + req := &dns.Msg{ Question: []dns.Question{{ - Name: ip.String(), - Qtype: dns.TypePTR, + Name: dns.Fqdn(rev), + Qclass: dns.ClassINET, + Qtype: dns.TypePTR, }}, } @@ -146,7 +155,9 @@ func TestRDNS_ensurePrivateCache(t *testing.T) { MaxCount: defaultRDNSCacheSize, }) - ex := &rDNSExchanger{} + ex := &rDNSExchanger{ + ex: aghtest.NewErrorUpstream(), + } rdns := &RDNS{ ipCache: ipCache, @@ -167,15 +178,27 @@ func TestRDNS_WorkerLoop(t *testing.T) { w := &bytes.Buffer{} aghtest.ReplaceLogWriter(t, w) - locUpstream := &aghtest.Upstream{ - Reverse: map[string][]string{ - "192.168.1.1": {"local.domain"}, - "2a00:1450:400c:c06::93": {"ipv6.domain"}, + localIP := net.IP{192, 168, 1, 1} + revIPv4, err := netutil.IPToReversedAddr(localIP) + require.NoError(t, err) + + revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93")) + 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) { + resp = aghalg.Coalesce( + aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv4, "local.domain"), + aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv6, "ipv6.domain"), + new(dns.Msg).SetRcode(req, dns.RcodeNameError), + ) + + return resp, nil }, } - errUpstream := &aghtest.TestErrUpstream{ - Err: errors.Error("1234"), - } + + errUpstream := aghtest.NewErrorUpstream() testCases := []struct { ups upstream.Upstream @@ -186,10 +209,10 @@ func TestRDNS_WorkerLoop(t *testing.T) { ups: locUpstream, wantLog: "", name: "all_good", - cliIP: net.IP{192, 168, 1, 1}, + cliIP: localIP, }, { ups: errUpstream, - wantLog: `rdns: resolving "192.168.1.2": errupstream: 1234`, + wantLog: `rdns: resolving "192.168.1.2": test upstream error`, name: "resolve_error", cliIP: net.IP{192, 168, 1, 2}, }, { @@ -211,9 +234,7 @@ func TestRDNS_WorkerLoop(t *testing.T) { ch := make(chan net.IP) rdns := &RDNS{ exchanger: &rDNSExchanger{ - ex: aghtest.Exchanger{ - Ups: tc.ups, - }, + ex: tc.ups, }, clients: cc, ipCh: ch, diff --git a/internal/home/service.go b/internal/home/service.go index 081974e2..831a80d0 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -433,8 +433,11 @@ EnvironmentFile=-/etc/sysconfig/{{.Name}} WantedBy=multi-user.target ` -// Note: we should keep it in sync with the template from service_sysv_linux.go file -// Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt +// sysvScript is the source of the daemon script for SysV-based Linux systems. +// Keep as close as possible to the https://github.com/kardianos/service/blob/29f8c79c511bc18422bb99992779f96e6bc33921/service_sysv_linux.go#L187. +// +// Use ps command instead of reading the procfs since it's a more +// implementation-independent approach. const sysvScript = `#!/bin/sh # For RedHat and cousins: # chkconfig: - 99 01 @@ -465,7 +468,7 @@ get_pid() { } is_running() { - [ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1 + [ -f "$pid_file" ] && ps -p "$(get_pid)" > /dev/null 2>&1 } case "$1" in @@ -609,7 +612,7 @@ command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirector run_rc_command "$1" ` -const openBSDScript = `#!/bin/sh +const openBSDScript = `#!/bin/ksh # # $OpenBSD: {{ .SvcInfo }} diff --git a/internal/home/service_linux.go b/internal/home/service_linux.go new file mode 100644 index 00000000..c885529b --- /dev/null +++ b/internal/home/service_linux.go @@ -0,0 +1,83 @@ +//go:build linux +// +build linux + +package home + +import ( + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/kardianos/service" +) + +func chooseSystem() { + sys := service.ChosenSystem() + // By default, package service uses the SysV system if it cannot detect + // anything other, but the update-rc.d fix should not be applied on OpenWrt, + // so exclude it explicitly. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/4480 and + // https://github.com/AdguardTeam/AdGuardHome/issues/4677. + if sys.String() == "unix-systemv" && !aghos.IsOpenWrt() { + service.ChooseSystem(sysvSystem{System: sys}) + } +} + +// sysvSystem is a wrapper for service.System that wraps the service.Service +// while creating a new one. +// +// TODO(e.burkov): File a PR to github.com/kardianos/service. +type sysvSystem struct { + // System is expected to have an unexported type + // *service.linuxSystemService. + service.System +} + +// New returns a wrapped service.Service. +func (sys sysvSystem) New(i service.Interface, c *service.Config) (s service.Service, err error) { + s, err = sys.System.New(i, c) + if err != nil { + return s, err + } + + return sysvService{ + Service: s, + name: c.Name, + }, nil +} + +// sysvService is a wrapper for a service.Service that also calls update-rc.d in +// a proper way on installing and uninstalling. +type sysvService struct { + // Service is expected to have an unexported type *service.sysv. + service.Service + // name stores the name of the service to call updating script with it. + name string +} + +// Install wraps service.Service.Install call with calling the updating script. +func (svc sysvService) Install() (err error) { + err = svc.Service.Install() + if err != nil { + // Don't wrap an error since it's informative enough as is. + return err + } + + _, _, err = aghos.RunCommand("update-rc.d", svc.name, "defaults") + + // Don't wrap an error since it's informative enough as is. + return err +} + +// Uninstall wraps service.Service.Uninstall call with calling the updating +// script. +func (svc sysvService) Uninstall() (err error) { + err = svc.Service.Uninstall() + if err != nil { + // Don't wrap an error since it's informative enough as is. + return err + } + + _, _, err = aghos.RunCommand("update-rc.d", svc.name, "remove") + + // Don't wrap an error since it's informative enough as is. + return err +} diff --git a/internal/home/service_openbsd.go b/internal/home/service_openbsd.go index 8ad0d212..beeabd04 100644 --- a/internal/home/service_openbsd.go +++ b/internal/home/service_openbsd.go @@ -160,7 +160,7 @@ rc_cmd $1 // template returns the script template to put into rc.d. func (s *openbsdRunComService) template() (t *template.Template) { - tf := map[string]interface{}{ + tf := map[string]any{ "args": func(sl []string) string { return `"` + strings.Join(sl, " ") + `"` }, @@ -390,42 +390,42 @@ func newSysLogger(_ string, _ chan<- error) (service.Logger, error) { type sysLogger struct{} // Error implements service.Logger interface for sysLogger. -func (sysLogger) Error(v ...interface{}) error { +func (sysLogger) Error(v ...any) error { log.Error(fmt.Sprint(v...)) return nil } // Warning implements service.Logger interface for sysLogger. -func (sysLogger) Warning(v ...interface{}) error { +func (sysLogger) Warning(v ...any) error { log.Info("warning: %s", fmt.Sprint(v...)) return nil } // Info implements service.Logger interface for sysLogger. -func (sysLogger) Info(v ...interface{}) error { +func (sysLogger) Info(v ...any) error { log.Info(fmt.Sprint(v...)) return nil } // Errorf implements service.Logger interface for sysLogger. -func (sysLogger) Errorf(format string, a ...interface{}) error { +func (sysLogger) Errorf(format string, a ...any) error { log.Error(format, a...) return nil } // Warningf implements service.Logger interface for sysLogger. -func (sysLogger) Warningf(format string, a ...interface{}) error { +func (sysLogger) Warningf(format string, a ...any) error { log.Info("warning: %s", fmt.Sprintf(format, a...)) return nil } // Infof implements service.Logger interface for sysLogger. -func (sysLogger) Infof(format string, a ...interface{}) error { +func (sysLogger) Infof(format string, a ...any) error { log.Info(format, a...) return nil diff --git a/internal/home/service_others.go b/internal/home/service_others.go index 83aa63ea..6e2afd10 100644 --- a/internal/home/service_others.go +++ b/internal/home/service_others.go @@ -1,6 +1,8 @@ -//go:build !openbsd -// +build !openbsd +//go:build !(openbsd || linux) +// +build !openbsd,!linux package home +// chooseSystem checks the current system detected and substitutes it with local +// implementation if needed. func chooseSystem() {} diff --git a/internal/home/tls.go b/internal/home/tls.go index fe595e98..7240496c 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -250,21 +250,17 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) { } if setts.Enabled { - uc := aghalg.UniqChecker{} - addPorts( - uc, + err = validatePorts( tcpPort(config.BindPort), tcpPort(config.BetaBindPort), - udpPort(config.DNS.Port), tcpPort(setts.PortHTTPS), tcpPort(setts.PortDNSOverTLS), - udpPort(setts.PortDNSOverQUIC), tcpPort(setts.PortDNSCrypt), + udpPort(config.DNS.Port), + udpPort(setts.PortDNSOverQUIC), ) - - err = uc.Validate(aghalg.IntIsBefore) if err != nil { - aghhttp.Error(r, w, http.StatusBadRequest, "validating ports: %s", err) + aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) return } @@ -343,19 +339,15 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) { } if data.Enabled { - uc := aghalg.UniqChecker{} - addPorts( - uc, + err = validatePorts( tcpPort(config.BindPort), tcpPort(config.BetaBindPort), - udpPort(config.DNS.Port), tcpPort(data.PortHTTPS), tcpPort(data.PortDNSOverTLS), - udpPort(data.PortDNSOverQUIC), tcpPort(data.PortDNSCrypt), + udpPort(config.DNS.Port), + udpPort(data.PortDNSOverQUIC), ) - - err = uc.Validate(aghalg.IntIsBefore) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) @@ -421,6 +413,38 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) { } } +// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home +// DNS protocols. +func validatePorts( + bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort, + dnsPort, doqPort udpPort, +) (err error) { + tcpPorts := aghalg.UniqChecker[tcpPort]{} + addPorts( + tcpPorts, + tcpPort(bindPort), + tcpPort(betaBindPort), + tcpPort(dohPort), + tcpPort(dotPort), + tcpPort(dnscryptTCPPort), + ) + + err = tcpPorts.Validate() + if err != nil { + return fmt.Errorf("validating tcp ports: %w", err) + } + + udpPorts := aghalg.UniqChecker[udpPort]{} + addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort)) + + err = udpPorts.Validate() + if err != nil { + return fmt.Errorf("validating udp ports: %w", err) + } + + return nil +} + func verifyCertChain(data *tlsConfigStatus, certChain, serverName string) error { log.Tracef("TLS: got certificate: %d bytes", len(certChain)) diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index d9611dc9..4426149c 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -1,6 +1,7 @@ package home import ( + "bytes" "fmt" "net/url" "os" @@ -17,19 +18,16 @@ import ( "github.com/AdguardTeam/golibs/timeutil" "github.com/google/renameio/maybe" "golang.org/x/crypto/bcrypt" - yaml "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v3" ) // currentSchemaVersion is the current schema version. const currentSchemaVersion = 14 // These aliases are provided for convenience. -// -// TODO(e.burkov): Remove any after updating to Go 1.18. type ( - any = interface{} yarr = []any - yobj = map[any]any + yobj = map[string]any ) // Performs necessary upgrade operations if needed @@ -107,16 +105,20 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { return fmt.Errorf("unknown configuration schema version %d", oldVersion) } - body, err := yaml.Marshal(diskConf) + buf := &bytes.Buffer{} + enc := yaml.NewEncoder(buf) + enc.SetIndent(2) + + err = enc.Encode(diskConf) if err != nil { return fmt.Errorf("generating new config: %w", err) } - config.fileData = body + config.fileData = buf.Bytes() confFile := config.getConfigFilename() - err = maybe.WriteFile(confFile, body, 0o644) + err = maybe.WriteFile(confFile, config.fileData, 0o644) if err != nil { - return fmt.Errorf("saving new config: %w", err) + return fmt.Errorf("writing new config: %w", err) } return nil @@ -176,16 +178,16 @@ func upgradeSchema2to3(diskConf yobj) error { return fmt.Errorf("no DNS configuration in config file") } - // Convert interface{} to yobj + // Convert any to yobj newDNSConfig := make(yobj) switch v := dnsConfig.(type) { - case map[interface{}]interface{}: + case yobj: for k, v := range v { newDNSConfig[fmt.Sprint(k)] = v } default: - return fmt.Errorf("dns configuration is not a map") + return fmt.Errorf("unexpected type of dns: %T", dnsConfig) } // Replace bootstrap_dns value filed with new array contains old bootstrap_dns inside @@ -216,12 +218,12 @@ func upgradeSchema3to4(diskConf yobj) error { } switch arr := clients.(type) { - case []interface{}: + case []any: for i := range arr { switch c := arr[i].(type) { - case map[interface{}]interface{}: + case map[any]any: c["use_global_blocked_services"] = true default: @@ -307,11 +309,11 @@ func upgradeSchema5to6(diskConf yobj) error { } switch arr := clients.(type) { - case []interface{}: + case []any: for i := range arr { switch c := arr[i].(type) { - case map[interface{}]interface{}: - var ipVal interface{} + case map[any]any: + var ipVal any ipVal, ok = c["ip"] ids := []string{} if ok { @@ -326,7 +328,7 @@ func upgradeSchema5to6(diskConf yobj) error { } } - var macVal interface{} + var macVal any macVal, ok = c["mac"] if ok { var mac string @@ -377,7 +379,7 @@ func upgradeSchema6to7(diskConf yobj) error { } switch dhcp := dhcpVal.(type) { - case map[interface{}]interface{}: + case map[any]any: var str string str, ok = dhcp["gateway_ip"].(string) if !ok { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index 4c25cba3..a5267032 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -190,7 +190,7 @@ func testDiskConf(schemaVersion int) (diskConf yobj) { return diskConf } -// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would +// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v3 would // unmarshal it. In YAML, keys aren't guaranteed to always only be strings. func testDNSConf(schemaVersion int) (dnsConf yobj) { dnsConf = yobj{ @@ -500,7 +500,7 @@ func TestUpgradeSchema11to12(t *testing.T) { dnsVal, ok = dns.(yobj) require.True(t, ok) - var ivl interface{} + var ivl any ivl, ok = dnsVal["querylog_interval"] require.True(t, ok) diff --git a/internal/querylog/http.go b/internal/querylog/http.go index 6a2bdcee..11f62d0d 100644 --- a/internal/querylog/http.go +++ b/internal/querylog/http.go @@ -19,10 +19,10 @@ import ( ) type qlogConfig struct { - Enabled bool `json:"enabled"` // Use float64 here to support fractional numbers and not mess the API // users by changing the units. Interval float64 `json:"interval"` + Enabled bool `json:"enabled"` AnonymizeClientIP bool `json:"anonymize_client_ip"` } diff --git a/internal/querylog/json.go b/internal/querylog/json.go index d6adebe4..24acd468 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -17,7 +17,7 @@ import ( // TODO(a.garipov): Use a proper structured approach here. // jobject is a JSON object alias. -type jobject = map[string]interface{} +type jobject = map[string]any // entriesToJSON converts query log entries to JSON. func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) { diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 8856fd9c..24eec40e 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -149,7 +149,7 @@ func (l *queryLog) clear() { log.Error("removing log file %q: %s", l.logFile, err) } - log.Debug("Query log: cleared") + log.Debug("querylog: cleared") } func (l *queryLog) Add(params *AddParams) { diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index fbfc459d..6beed1be 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -285,8 +285,8 @@ func addEntry(l *queryLog, host string, answerStr, client net.IP) { Answer: &a, OrigAnswer: &a, Result: &res, - ClientIP: client, Upstream: "upstream", + ClientIP: client, } l.Add(params) diff --git a/internal/querylog/qlogfile_test.go b/internal/querylog/qlogfile_test.go index ff1a53b3..3e32420f 100644 --- a/internal/querylog/qlogfile_test.go +++ b/internal/querylog/qlogfile_test.go @@ -303,7 +303,7 @@ func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) { func TestQLog_Seek(t *testing.T) { const nl = "\n" const strV = "%s" - const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}` + nl + + const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://unfiltered.adguard-dns.com:853"}` + nl + `{"T":"` + strV + `"}` + nl + `{"T":"` + strV + `"}` + nl timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00") diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go index bd6e1569..2d8e397f 100644 --- a/internal/querylog/querylog.go +++ b/internal/querylog/querylog.go @@ -2,10 +2,10 @@ package querylog import ( "net" - "net/http" "path/filepath" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/golibs/errors" @@ -28,14 +28,17 @@ type QueryLog interface { WriteDiskConfig(c *Config) } -// Config - configuration object +// Config is the query log configuration structure. type Config struct { + // Anonymizer processes the IP addresses to anonymize those if needed. + Anonymizer *aghnet.IPMut + // ConfigModified is called when the configuration is changed, for // example by HTTP requests. ConfigModified func() // HTTPRegister registers an HTTP handler. - HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) + HTTPRegister aghhttp.RegisterFunc // FindClient returns client information by their IDs. FindClient func(ids []string) (c *Client, err error) @@ -68,9 +71,6 @@ type Config struct { // AnonymizeClientIP tells if the query log should anonymize clients' IP // addresses. AnonymizeClientIP bool - - // Anonymizer processes the IP addresses to anonymize those if needed. - Anonymizer *aghnet.IPMut } // AddParams is the parameters for adding an entry. @@ -91,18 +91,18 @@ type AddParams struct { // Result is the filtering result (optional). Result *filtering.Result - // Elapsed is the time spent for processing the request. - Elapsed time.Duration - ClientID string - ClientIP net.IP - // Upstream is the URL of the upstream DNS server. Upstream string ClientProto ClientProto + ClientIP net.IP + + // Elapsed is the time spent for processing the request. + Elapsed time.Duration + // Cached indicates if the response is served from cache. Cached bool diff --git a/internal/querylog/search.go b/internal/querylog/search.go index 4a3de979..8fb32e60 100644 --- a/internal/querylog/search.go +++ b/internal/querylog/search.go @@ -73,7 +73,7 @@ func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entrie // search - searches log entries in the query log using specified parameters // returns the list of entries found + time of the oldest entry -func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) { +func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest time.Time) { now := time.Now() if params.limit == 0 { @@ -88,7 +88,7 @@ func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) { totalLimit := params.offset + params.limit // now let's get a unified collection - entries := append(memoryEntries, fileEntries...) + entries = append(memoryEntries, fileEntries...) if len(entries) > totalLimit { // remove extra records entries = entries[:totalLimit] @@ -111,13 +111,18 @@ func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) { } } - if len(entries) > 0 && len(entries) <= totalLimit { + if len(entries) > 0 { // Update oldest after merging in the memory buffer. oldest = entries[len(entries)-1].Time } - log.Debug("QueryLog: prepared data (%d/%d) older than %s in %s", - len(entries), total, params.olderThan, time.Since(now)) + log.Debug( + "querylog: prepared data (%d/%d) older than %s in %s", + len(entries), + total, + params.olderThan, + time.Since(now), + ) return entries, oldest } @@ -180,6 +185,8 @@ func (l *queryLog) searchFiles( e, ts, err = l.readNextEntry(r, params, cache) if err != nil { if err == io.EOF { + oldestNano = 0 + break } diff --git a/internal/stats/http.go b/internal/stats/http.go index e2f00039..ae980bf3 100644 --- a/internal/stats/http.go +++ b/internal/stats/http.go @@ -5,6 +5,7 @@ package stats import ( "encoding/json" "net/http" + "sync/atomic" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" @@ -15,18 +16,10 @@ import ( // The key is either a client's address or a requested address. type topAddrs = map[string]uint64 -// statsResponse is a response for getting statistics. -type statsResponse struct { +// StatsResp is a response to the GET /control/stats. +type StatsResp struct { TimeUnits string `json:"time_units"` - NumDNSQueries uint64 `json:"num_dns_queries"` - NumBlockedFiltering uint64 `json:"num_blocked_filtering"` - NumReplacedSafebrowsing uint64 `json:"num_replaced_safebrowsing"` - NumReplacedSafesearch uint64 `json:"num_replaced_safesearch"` - NumReplacedParental uint64 `json:"num_replaced_parental"` - - AvgProcessingTime float64 `json:"avg_processing_time"` - TopQueried []topAddrs `json:"top_queried_domains"` TopClients []topAddrs `json:"top_clients"` TopBlocked []topAddrs `json:"top_blocked_domains"` @@ -36,37 +29,30 @@ type statsResponse struct { BlockedFiltering []uint64 `json:"blocked_filtering"` ReplacedSafebrowsing []uint64 `json:"replaced_safebrowsing"` ReplacedParental []uint64 `json:"replaced_parental"` + + NumDNSQueries uint64 `json:"num_dns_queries"` + NumBlockedFiltering uint64 `json:"num_blocked_filtering"` + NumReplacedSafebrowsing uint64 `json:"num_replaced_safebrowsing"` + NumReplacedSafesearch uint64 `json:"num_replaced_safesearch"` + NumReplacedParental uint64 `json:"num_replaced_parental"` + + AvgProcessingTime float64 `json:"avg_processing_time"` } -// handleStats is a handler for getting statistics. -func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) { +// handleStats handles requests to the GET /control/stats endpoint. +func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) { + limit := atomic.LoadUint32(&s.limitHours) + start := time.Now() + resp, ok := s.getData(limit) + log.Debug("stats: prepared data in %v", time.Since(start)) - var resp statsResponse - if s.conf.limit == 0 { - resp = statsResponse{ - TimeUnits: "days", + if !ok { + // Don't bring the message to the lower case since it's a part of UI + // text for the moment. + aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get statistics data") - TopBlocked: []topAddrs{}, - TopClients: []topAddrs{}, - TopQueried: []topAddrs{}, - - BlockedFiltering: []uint64{}, - DNSQueries: []uint64{}, - ReplacedParental: []uint64{}, - ReplacedSafebrowsing: []uint64{}, - } - } else { - var ok bool - resp, ok = s.getData() - - log.Debug("stats: prepared data in %v", time.Since(start)) - - if !ok { - aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get statistics data") - - return - } + return } w.Header().Set("Content-Type", "application/json") @@ -74,36 +60,30 @@ func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) { err := json.NewEncoder(w).Encode(resp) if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err) - - return } } -type config struct { +// configResp is the response to the GET /control/stats_info. +type configResp struct { IntervalDays uint32 `json:"interval"` } -// Get configuration -func (s *statsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) { - resp := config{} - resp.IntervalDays = s.conf.limit / 24 +// handleStatsInfo handles requests to the GET /control/stats_info endpoint. +func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) { + resp := configResp{IntervalDays: atomic.LoadUint32(&s.limitHours) / 24} - data, err := json.Marshal(resp) + w.Header().Set("Content-Type", "application/json") + + err := json.NewEncoder(w).Encode(resp) if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err) - - return - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(data) - if err != nil { - aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err) } } -// Set configuration -func (s *statsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) { - reqData := config{} +// handleStatsConfig handles requests to the POST /control/stats_config +// endpoint. +func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) { + reqData := configResp{} err := json.NewDecoder(r.Body).Decode(&reqData) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err) @@ -118,22 +98,25 @@ func (s *statsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) { } s.setLimit(int(reqData.IntervalDays)) - s.conf.ConfigModified() + s.configModified() } -// Reset data -func (s *statsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) { - s.clear() +// handleStatsReset handles requests to the POST /control/stats_reset endpoint. +func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) { + err := s.clear() + if err != nil { + aghhttp.Error(r, w, http.StatusInternalServerError, "stats: %s", err) + } } -// Register web handlers -func (s *statsCtx) initWeb() { - if s.conf.HTTPRegister == nil { +// initWeb registers the handlers for web endpoints of statistics module. +func (s *StatsCtx) initWeb() { + if s.httpRegister == nil { return } - s.conf.HTTPRegister(http.MethodGet, "/control/stats", s.handleStats) - s.conf.HTTPRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset) - s.conf.HTTPRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig) - s.conf.HTTPRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo) + s.httpRegister(http.MethodGet, "/control/stats", s.handleStats) + s.httpRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset) + s.httpRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig) + s.httpRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo) } diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 2944a163..aea6d92d 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -3,86 +3,545 @@ package stats import ( + "fmt" + "io" "net" - "net/http" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" + "go.etcd.io/bbolt" ) -type unitIDCallback func() uint32 - -// DiskConfig - configuration settings that are stored on disk +// DiskConfig is the configuration structure that is stored in file. type DiskConfig struct { - Interval uint32 `yaml:"statistics_interval"` // time interval for statistics (in days) + // Interval is the number of days for which the statistics are collected + // before flushing to the database. + Interval uint32 `yaml:"statistics_interval"` } -// Config - module configuration -type Config struct { - Filename string // database file name - LimitDays uint32 // time limit (in days) - UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used. +// checkInterval returns true if days is valid to be used as statistics +// retention interval. The valid values are 0, 1, 7, 30 and 90. +func checkInterval(days uint32) (ok bool) { + return days == 0 || days == 1 || days == 7 || days == 30 || days == 90 +} - // Called when the configuration is changed by HTTP request +// Config is the configuration structure for the statistics collecting. +type Config struct { + // UnitID is the function to generate the identifier for current unit. If + // nil, the default function is used, see newUnitID. + UnitID UnitIDGenFunc + + // ConfigModified will be called each time the configuration changed via web + // interface. ConfigModified func() - // Register an HTTP handler - HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) + // HTTPRegister is the function that registers handlers for the stats + // endpoints. + HTTPRegister aghhttp.RegisterFunc - limit uint32 // maximum time we need to keep data for (in hours) + // Filename is the name of the database file. + Filename string + + // LimitDays is the maximum number of days to collect statistics into the + // current unit. + LimitDays uint32 } -// New - create object -func New(conf Config) (Stats, error) { - return createObject(conf) -} - -// Stats - main interface -type Stats interface { +// Interface is the statistics interface to be used by other packages. +type Interface interface { + // Start begins the statistics collecting. Start() - // Close object. - // This function is not thread safe - // (can't be called in parallel with any other function of this interface). - Close() + io.Closer - // Update counters + // Update collects the incoming statistics data. Update(e Entry) - // Get IP addresses of the clients with the most number of requests - GetTopClientsIP(limit uint) []net.IP + // GetTopClientIP returns at most limit IP addresses corresponding to the + // clients with the most number of requests. + TopClientsIP(limit uint) []net.IP - // WriteDiskConfig - write configuration + // WriteDiskConfig puts the Interface's configuration to the dc. WriteDiskConfig(dc *DiskConfig) } -// TimeUnit - time unit -type TimeUnit int - -// Supported time units -const ( - Hours TimeUnit = iota - Days -) - -// Result of DNS request processing -type Result int - -// Supported result values -const ( - RNotFiltered Result = iota + 1 - RFiltered - RSafeBrowsing - RSafeSearch - RParental - rLast -) - -// Entry is a statistics data entry. -type Entry struct { - // Clients is the client's primary ID. +// StatsCtx collects the statistics and flushes it to the database. Its default +// flushing interval is one hour. +// +// TODO(e.burkov): Use atomic.Pointer for accessing db in go1.19. +type StatsCtx struct { + // limitHours is the maximum number of hours to collect statistics into the + // current unit. // - // TODO(a.garipov): Make this a {net.IP, string} enum? - Client string + // It is of type uint32 to be accessed by atomic. It's arranged at the + // beginning of the structure to keep 64-bit alignment. + limitHours uint32 - Domain string - Result Result - Time uint32 // processing time (msec) + // currMu protects curr. + currMu *sync.RWMutex + // curr is the actual statistics collection result. + curr *unit + + // dbMu protects db. + dbMu *sync.Mutex + // db is the opened statistics database, if any. + db *bbolt.DB + + // unitIDGen is the function that generates an identifier for the current + // unit. It's here for only testing purposes. + unitIDGen UnitIDGenFunc + + // httpRegister is used to set HTTP handlers. + httpRegister aghhttp.RegisterFunc + + // configModified is called whenever the configuration is modified via web + // interface. + configModified func() + + // filename is the name of database file. + filename string +} + +var _ Interface = &StatsCtx{} + +// New creates s from conf and properly initializes it. Don't use s before +// calling it's Start method. +func New(conf Config) (s *StatsCtx, err error) { + defer withRecovered(&err) + + s = &StatsCtx{ + currMu: &sync.RWMutex{}, + dbMu: &sync.Mutex{}, + filename: conf.Filename, + configModified: conf.ConfigModified, + httpRegister: conf.HTTPRegister, + } + if s.limitHours = conf.LimitDays * 24; !checkInterval(conf.LimitDays) { + s.limitHours = 24 + } + if s.unitIDGen = newUnitID; conf.UnitID != nil { + s.unitIDGen = conf.UnitID + } + + // TODO(e.burkov): Move the code below to the Start method. + + err = s.openDB() + if err != nil { + return nil, fmt.Errorf("opening database: %w", err) + } + + var udb *unitDB + id := s.unitIDGen() + + tx, err := s.db.Begin(true) + if err != nil { + return nil, fmt.Errorf("stats: opening a transaction: %w", err) + } + + deleted := deleteOldUnits(tx, id-s.limitHours-1) + udb = loadUnitFromDB(tx, id) + + err = finishTxn(tx, deleted > 0) + if err != nil { + log.Error("stats: %s", err) + } + + s.curr = newUnit(id) + s.curr.deserialize(udb) + + log.Debug("stats: initialized") + + return s, nil +} + +// withRecovered turns the value recovered from panic if any into an error and +// combines it with the one pointed by orig. orig must be non-nil. +func withRecovered(orig *error) { + p := recover() + if p == nil { + return + } + + var err error + switch p := p.(type) { + case error: + err = fmt.Errorf("panic: %w", p) + default: + err = fmt.Errorf("panic: recovered value of type %[1]T: %[1]v", p) + } + + *orig = errors.WithDeferred(*orig, err) +} + +// Start implements the Interface interface for *StatsCtx. +func (s *StatsCtx) Start() { + s.initWeb() + + go s.periodicFlush() +} + +// Close implements the io.Closer interface for *StatsCtx. +func (s *StatsCtx) Close() (err error) { + defer func() { err = errors.Annotate(err, "stats: closing: %w") }() + + db := s.swapDatabase(nil) + if db == nil { + return nil + } + defer func() { + cerr := db.Close() + if cerr == nil { + log.Debug("stats: database closed") + } + + err = errors.WithDeferred(err, cerr) + }() + + tx, err := db.Begin(true) + if err != nil { + return fmt.Errorf("opening transaction: %w", err) + } + defer func() { err = errors.WithDeferred(err, finishTxn(tx, err == nil)) }() + + s.currMu.RLock() + defer s.currMu.RUnlock() + + udb := s.curr.serialize() + + return udb.flushUnitToDB(tx, s.curr.id) +} + +// Update implements the Interface interface for *StatsCtx. +func (s *StatsCtx) Update(e Entry) { + if atomic.LoadUint32(&s.limitHours) == 0 { + return + } + + if e.Result == 0 || e.Result >= resultLast || e.Domain == "" || e.Client == "" { + log.Debug("stats: malformed entry") + + return + } + + s.currMu.Lock() + defer s.currMu.Unlock() + + if s.curr == nil { + log.Error("stats: current unit is nil") + + return + } + + clientID := e.Client + if ip := net.ParseIP(clientID); ip != nil { + clientID = ip.String() + } + + s.curr.add(e.Result, e.Domain, clientID, uint64(e.Time)) +} + +// WriteDiskConfig implements the Interface interface for *StatsCtx. +func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) { + dc.Interval = atomic.LoadUint32(&s.limitHours) / 24 +} + +// TopClientsIP implements the Interface interface for *StatsCtx. +func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) { + limit := atomic.LoadUint32(&s.limitHours) + if limit == 0 { + return nil + } + + units, _ := s.loadUnits(limit) + if units == nil { + return nil + } + + // Collect data for all the clients to sort and crop it afterwards. + m := map[string]uint64{} + for _, u := range units { + for _, it := range u.Clients { + m[it.Name] += it.Count + } + } + + a := convertMapToSlice(m, int(maxCount)) + ips = []net.IP{} + for _, it := range a { + ip := net.ParseIP(it.Name) + if ip != nil { + ips = append(ips, ip) + } + } + + return ips +} + +// database returns the database if it's opened. It's safe for concurrent use. +func (s *StatsCtx) database() (db *bbolt.DB) { + s.dbMu.Lock() + defer s.dbMu.Unlock() + + return s.db +} + +// swapDatabase swaps the database with another one and returns it. It's safe +// for concurrent use. +func (s *StatsCtx) swapDatabase(with *bbolt.DB) (old *bbolt.DB) { + s.dbMu.Lock() + defer s.dbMu.Unlock() + + old, s.db = s.db, with + + return old +} + +// deleteOldUnits walks the buckets available to tx and deletes old units. It +// returns the number of deletions performed. +func deleteOldUnits(tx *bbolt.Tx, firstID uint32) (deleted int) { + log.Debug("stats: deleting old units until id %d", firstID) + + // TODO(a.garipov): See if this is actually necessary. Looks like a rather + // bizarre solution. + const errStop errors.Error = "stop iteration" + + walk := func(name []byte, _ *bbolt.Bucket) (err error) { + nameID, ok := unitNameToID(name) + if ok && nameID >= firstID { + return errStop + } + + err = tx.DeleteBucket(name) + if err != nil { + log.Debug("stats: deleting bucket: %s", err) + + return nil + } + + log.Debug("stats: deleted unit %d (name %x)", nameID, name) + + deleted++ + + return nil + } + + err := tx.ForEach(walk) + if err != nil && !errors.Is(err, errStop) { + log.Debug("stats: deleting units: %s", err) + } + + return deleted +} + +// openDB returns an error if the database can't be opened from the specified +// file. It's safe for concurrent use. +func (s *StatsCtx) openDB() (err error) { + log.Debug("stats: opening database") + + var db *bbolt.DB + db, err = bbolt.Open(s.filename, 0o644, nil) + if err != nil { + if err.Error() == "invalid argument" { + log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations") + } + + return err + } + + // Use defer to unlock the mutex as soon as possible. + defer log.Debug("stats: database opened") + + s.dbMu.Lock() + defer s.dbMu.Unlock() + + s.db = db + + return nil +} + +func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) { + id := s.unitIDGen() + + s.currMu.Lock() + defer s.currMu.Unlock() + + ptr := s.curr + if ptr == nil { + return false, 0 + } + + limit := atomic.LoadUint32(&s.limitHours) + if limit == 0 || ptr.id == id { + return true, time.Second + } + + db := s.database() + if db == nil { + return true, 0 + } + + isCommitable := true + tx, err := db.Begin(true) + if err != nil { + log.Error("stats: opening transaction: %s", err) + + return true, 0 + } + defer func() { + if err = finishTxn(tx, isCommitable); err != nil { + log.Error("stats: %s", err) + } + }() + + s.curr = newUnit(id) + + flushErr := ptr.serialize().flushUnitToDB(tx, ptr.id) + if flushErr != nil { + log.Error("stats: flushing unit: %s", flushErr) + isCommitable = false + } + + delErr := tx.DeleteBucket(idToUnitName(id - limit)) + if delErr != nil { + // TODO(e.burkov): Improve the algorithm of deleting the oldest bucket + // to avoid the error. + if errors.Is(delErr, bbolt.ErrBucketNotFound) { + log.Debug("stats: warning: deleting unit: %s", delErr) + } else { + isCommitable = false + log.Error("stats: deleting unit: %s", delErr) + } + } + + return true, 0 +} + +// periodicFlush checks and flushes the unit to the database if the freshly +// generated unit ID differs from the current's ID. Flushing process includes: +// - swapping the current unit with the new empty one; +// - writing the current unit to the database; +// - removing the stale unit from the database. +func (s *StatsCtx) periodicFlush() { + for cont, sleepFor := true, time.Duration(0); cont; time.Sleep(sleepFor) { + cont, sleepFor = s.flush() + } + + log.Debug("periodic flushing finished") +} + +func (s *StatsCtx) setLimit(limitDays int) { + atomic.StoreUint32(&s.limitHours, uint32(24*limitDays)) + if limitDays == 0 { + if err := s.clear(); err != nil { + log.Error("stats: %s", err) + } + } + + log.Debug("stats: set limit: %d days", limitDays) +} + +// Reset counters and clear database +func (s *StatsCtx) clear() (err error) { + defer func() { err = errors.Annotate(err, "clearing: %w") }() + + db := s.swapDatabase(nil) + if db != nil { + var tx *bbolt.Tx + tx, err = db.Begin(true) + if err != nil { + log.Error("stats: opening a transaction: %s", err) + } else if err = finishTxn(tx, false); err != nil { + // Don't wrap the error since it's informative enough as is. + return err + } + + // Active transactions will continue using database, but new ones won't + // be created. + err = db.Close() + if err != nil { + return fmt.Errorf("closing database: %w", err) + } + + // All active transactions are now closed. + log.Debug("stats: database closed") + } + + err = os.Remove(s.filename) + if err != nil { + log.Error("stats: %s", err) + } + + err = s.openDB() + if err != nil { + log.Error("stats: opening database: %s", err) + } + + // Use defer to unlock the mutex as soon as possible. + defer log.Debug("stats: cleared") + + s.currMu.Lock() + defer s.currMu.Unlock() + + s.curr = newUnit(s.unitIDGen()) + + return nil +} + +func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) { + db := s.database() + if db == nil { + return nil, 0 + } + + // Use writable transaction to ensure any ongoing writable transaction is + // taken into account. + tx, err := db.Begin(true) + if err != nil { + log.Error("stats: opening transaction: %s", err) + + return nil, 0 + } + + s.currMu.RLock() + defer s.currMu.RUnlock() + + cur := s.curr + + var curID uint32 + if cur != nil { + curID = cur.id + } else { + curID = s.unitIDGen() + } + + // Per-hour units. + units = make([]*unitDB, 0, limit) + firstID = curID - limit + 1 + for i := firstID; i != curID; i++ { + u := loadUnitFromDB(tx, i) + if u == nil { + u = &unitDB{NResult: make([]uint64, resultLast)} + } + units = append(units, u) + } + + err = finishTxn(tx, false) + if err != nil { + log.Error("stats: %s", err) + } + + if cur != nil { + units = append(units, cur.serialize()) + } + + if unitsLen := len(units); unitsLen != int(limit) { + log.Fatalf("loaded %d units whilst the desired number is %d", unitsLen, limit) + } + + return units, firstID } diff --git a/internal/stats/stats_internal_test.go b/internal/stats/stats_internal_test.go new file mode 100644 index 00000000..28a556d3 --- /dev/null +++ b/internal/stats/stats_internal_test.go @@ -0,0 +1,26 @@ +package stats + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TODO(e.burkov): Use more realistic data. +func TestStatsCollector(t *testing.T) { + ng := func(_ *unitDB) uint64 { return 0 } + units := make([]*unitDB, 720) + + t.Run("hours", func(t *testing.T) { + statsData := statsCollector(units, 0, Hours, ng) + assert.Len(t, statsData, 720) + }) + + t.Run("days", func(t *testing.T) { + for i := 0; i != 25; i++ { + statsData := statsCollector(units, uint32(i), Days, ng) + require.Lenf(t, statsData, 30, "i=%d", i) + } + }) +} diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 70b71db8..5d86024b 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -1,13 +1,17 @@ -package stats +package stats_test import ( + "encoding/json" "fmt" "net" - "os" + "net/http" + "net/http/httptest" + "path/filepath" "sync/atomic" "testing" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,147 +21,176 @@ func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } -func UIntArrayEquals(a, b []uint64) bool { - if len(a) != len(b) { - return false +// constUnitID is the UnitIDGenFunc which always return 0. +func constUnitID() (id uint32) { return 0 } + +func assertSuccessAndUnmarshal(t *testing.T, to any, handler http.Handler, req *http.Request) { + t.Helper() + + require.NotNil(t, handler) + + rw := httptest.NewRecorder() + + handler.ServeHTTP(rw, req) + require.Equal(t, http.StatusOK, rw.Code) + + data := rw.Body.Bytes() + if to == nil { + assert.Empty(t, data) + + return } - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true + err := json.Unmarshal(data, to) + require.NoError(t, err) } func TestStats(t *testing.T) { - conf := Config{ - Filename: "./stats.db", + cliIP := net.IP{127, 0, 0, 1} + cliIPStr := cliIP.String() + + handlers := map[string]http.Handler{} + conf := stats.Config{ + Filename: filepath.Join(t.TempDir(), "stats.db"), LimitDays: 1, + UnitID: constUnitID, + HTTPRegister: func(_, url string, handler http.HandlerFunc) { + handlers[url] = handler + }, } - s, err := createObject(conf) + s, err := stats.New(conf) require.NoError(t, err) - testutil.CleanupAndRequireSuccess(t, func() (err error) { - s.clear() - s.Close() - return os.Remove(conf.Filename) + s.Start() + testutil.CleanupAndRequireSuccess(t, s.Close) + + t.Run("data", func(t *testing.T) { + const reqDomain = "domain" + + entries := []stats.Entry{{ + Domain: reqDomain, + Client: cliIPStr, + Result: stats.RFiltered, + Time: 123456, + }, { + Domain: reqDomain, + Client: cliIPStr, + Result: stats.RNotFiltered, + Time: 123456, + }} + + wantData := &stats.StatsResp{ + TimeUnits: "hours", + TopQueried: []map[string]uint64{0: {reqDomain: 1}}, + TopClients: []map[string]uint64{0: {cliIPStr: 2}}, + TopBlocked: []map[string]uint64{0: {reqDomain: 1}}, + DNSQueries: []uint64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + }, + BlockedFiltering: []uint64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + }, + ReplacedSafebrowsing: []uint64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + ReplacedParental: []uint64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + NumDNSQueries: 2, + NumBlockedFiltering: 1, + NumReplacedSafebrowsing: 0, + NumReplacedSafesearch: 0, + NumReplacedParental: 0, + AvgProcessingTime: 0.123456, + } + + for _, e := range entries { + s.Update(e) + } + + data := &stats.StatsResp{} + req := httptest.NewRequest(http.MethodGet, "/control/stats", nil) + assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req) + + assert.Equal(t, wantData, data) }) - s.Update(Entry{ - Domain: "domain", - Client: "127.0.0.1", - Result: RFiltered, - Time: 123456, - }) - s.Update(Entry{ - Domain: "domain", - Client: "127.0.0.1", - Result: RNotFiltered, - Time: 123456, + t.Run("tops", func(t *testing.T) { + topClients := s.TopClientsIP(2) + require.NotEmpty(t, topClients) + + assert.True(t, cliIP.Equal(topClients[0])) }) - d, ok := s.getData() - require.True(t, ok) + t.Run("reset", func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/control/stats_reset", nil) + assertSuccessAndUnmarshal(t, nil, handlers["/control/stats_reset"], req) - a := []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} - assert.True(t, UIntArrayEquals(d.DNSQueries, a)) + _24zeroes := [24]uint64{} + emptyData := &stats.StatsResp{ + TimeUnits: "hours", + TopQueried: []map[string]uint64{}, + TopClients: []map[string]uint64{}, + TopBlocked: []map[string]uint64{}, + DNSQueries: _24zeroes[:], + BlockedFiltering: _24zeroes[:], + ReplacedSafebrowsing: _24zeroes[:], + ReplacedParental: _24zeroes[:], + } - a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - assert.True(t, UIntArrayEquals(d.BlockedFiltering, a)) + req = httptest.NewRequest(http.MethodGet, "/control/stats", nil) + data := &stats.StatsResp{} - a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - assert.True(t, UIntArrayEquals(d.ReplacedSafebrowsing, a)) - - a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - assert.True(t, UIntArrayEquals(d.ReplacedParental, a)) - - m := d.TopQueried - require.NotEmpty(t, m) - assert.EqualValues(t, 1, m[0]["domain"]) - - m = d.TopBlocked - require.NotEmpty(t, m) - assert.EqualValues(t, 1, m[0]["domain"]) - - m = d.TopClients - require.NotEmpty(t, m) - assert.EqualValues(t, 2, m[0]["127.0.0.1"]) - - assert.EqualValues(t, 2, d.NumDNSQueries) - assert.EqualValues(t, 1, d.NumBlockedFiltering) - assert.EqualValues(t, 0, d.NumReplacedSafebrowsing) - assert.EqualValues(t, 0, d.NumReplacedSafesearch) - assert.EqualValues(t, 0, d.NumReplacedParental) - assert.EqualValues(t, 0.123456, d.AvgProcessingTime) - - topClients := s.GetTopClientsIP(2) - require.NotEmpty(t, topClients) - assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0])) + assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req) + assert.Equal(t, emptyData, data) + }) } func TestLargeNumbers(t *testing.T) { - var hour int32 = 0 - newID := func() uint32 { - // Use "atomic" to make go race detector happy. - return uint32(atomic.LoadInt32(&hour)) + var curHour uint32 = 1 + handlers := map[string]http.Handler{} + + conf := stats.Config{ + Filename: filepath.Join(t.TempDir(), "stats.db"), + LimitDays: 1, + UnitID: func() (id uint32) { return atomic.LoadUint32(&curHour) }, + HTTPRegister: func(_, url string, handler http.HandlerFunc) { handlers[url] = handler }, } - conf := Config{ - Filename: "./stats.db", - LimitDays: 1, - UnitID: newID, - } - s, err := createObject(conf) + s, err := stats.New(conf) require.NoError(t, err) - testutil.CleanupAndRequireSuccess(t, func() (err error) { - s.Close() - return os.Remove(conf.Filename) - }) + s.Start() + testutil.CleanupAndRequireSuccess(t, s.Close) - // Number of distinct clients and domains every hour. - const n = 1000 + const ( + hoursNum = 12 + cliNumPerHour = 1000 + ) - for h := 0; h < 12; h++ { - atomic.AddInt32(&hour, 1) - for i := 0; i < n; i++ { - s.Update(Entry{ - Domain: fmt.Sprintf("domain%d", i), - Client: net.IP{ - 127, - 0, - byte((i & 0xff00) >> 8), - byte(i & 0xff), - }.String(), - Result: RNotFiltered, + req := httptest.NewRequest(http.MethodGet, "/control/stats", nil) + + for h := 0; h < hoursNum; h++ { + atomic.AddUint32(&curHour, 1) + + for i := 0; i < cliNumPerHour; i++ { + ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)} + e := stats.Entry{ + Domain: fmt.Sprintf("domain%d.hour%d", i, h), + Client: ip.String(), + Result: stats.RNotFiltered, Time: 123456, - }) + } + s.Update(e) } } - d, ok := s.getData() - require.True(t, ok) - assert.EqualValues(t, hour*n, d.NumDNSQueries) -} - -func TestStatsCollector(t *testing.T) { - ng := func(_ *unitDB) uint64 { - return 0 - } - units := make([]*unitDB, 720) - - t.Run("hours", func(t *testing.T) { - statsData := statsCollector(units, 0, Hours, ng) - assert.Len(t, statsData, 720) - }) - - t.Run("days", func(t *testing.T) { - for i := 0; i != 25; i++ { - statsData := statsCollector(units, uint32(i), Days, ng) - require.Lenf(t, statsData, 30, "i=%d", i) - } - }) + data := &stats.StatsResp{} + assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req) + assert.Equal(t, hoursNum*cliNumPerHour, int(data.NumDNSQueries)) } diff --git a/internal/stats/unit.go b/internal/stats/unit.go index 35d47a51..28e0b2bc 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -5,253 +5,148 @@ import ( "encoding/binary" "encoding/gob" "fmt" - "net" - "os" "sort" - "sync" "time" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - bolt "go.etcd.io/bbolt" + "go.etcd.io/bbolt" ) // TODO(a.garipov): Rewrite all of this. Add proper error handling and // inspection. Improve logging. Decrease complexity. const ( - maxDomains = 100 // max number of top domains to store in file or return via Get() - maxClients = 100 // max number of top clients to store in file or return via Get() + // maxDomains is the max number of top domains to return. + maxDomains = 100 + // maxClients is the max number of top clients to return. + maxClients = 100 ) -// statsCtx - global context -type statsCtx struct { - // mu protects unit. - mu *sync.Mutex - // current is the actual statistics collection result. - current *unit +// UnitIDGenFunc is the signature of a function that generates a unique ID for +// the statistics unit. +type UnitIDGenFunc func() (id uint32) - db *bolt.DB - conf *Config +// TimeUnit is the unit of measuring time while aggregating the statistics. +type TimeUnit int + +// Supported TimeUnit values. +const ( + Hours TimeUnit = iota + Days +) + +// Result is the resulting code of processing the DNS request. +type Result int + +// Supported Result values. +// +// TODO(e.burkov): Think about better naming. +const ( + RNotFiltered Result = iota + 1 + RFiltered + RSafeBrowsing + RSafeSearch + RParental + + resultLast = RParental + 1 +) + +// Entry is a statistics data entry. +type Entry struct { + // Clients is the client's primary ID. + // + // TODO(a.garipov): Make this a {net.IP, string} enum? + Client string + + // Domain is the domain name requested. + Domain string + + // Result is the result of processing the request. + Result Result + + // Time is the duration of the request processing in milliseconds. + Time uint32 } -// data for 1 time unit +// unit collects the statistics data for a specific period of time. type unit struct { - id uint32 // unit ID. Default: absolute hour since Jan 1, 1970 + // id is the unique unit's identifier. It's set to an absolute hour number + // since the beginning of UNIX time by the default ID generating function. + // + // Must not be rewritten after creating to be accessed concurrently without + // using mu. + id uint32 - nTotal uint64 // total requests - nResult []uint64 // number of requests per one result - timeSum uint64 // sum of processing time of all requests (usec) + // nTotal stores the total number of requests. + nTotal uint64 + // nResult stores the number of requests grouped by it's result. + nResult []uint64 + // timeSum stores the sum of processing time in milliseconds of each request + // written by the unit. + timeSum uint64 - // top: - domains map[string]uint64 // number of requests per domain - blockedDomains map[string]uint64 // number of blocked requests per domain - clients map[string]uint64 // number of requests per client + // domains stores the number of requests for each domain. + domains map[string]uint64 + // blockedDomains stores the number of requests for each domain that has + // been blocked. + blockedDomains map[string]uint64 + // clients stores the number of requests from each client. + clients map[string]uint64 } -// name-count pair +// newUnit allocates the new *unit. +func newUnit(id uint32) (u *unit) { + return &unit{ + id: id, + nResult: make([]uint64, resultLast), + domains: make(map[string]uint64), + blockedDomains: make(map[string]uint64), + clients: make(map[string]uint64), + } +} + +// countPair is a single name-number pair for deserializing statistics data into +// the database. type countPair struct { Name string Count uint64 } -// structure for storing data in file +// unitDB is the structure for serializing statistics data into the database. type unitDB struct { - NTotal uint64 + // NTotal is the total number of requests. + NTotal uint64 + // NResult is the number of requests by the result's kind. NResult []uint64 - Domains []countPair + // Domains is the number of requests for each domain name. + Domains []countPair + // BlockedDomains is the number of requests blocked for each domain name. BlockedDomains []countPair - Clients []countPair + // Clients is the number of requests from each client. + Clients []countPair - TimeAvg uint32 // usec + // TimeAvg is the average of processing times in milliseconds of all the + // requests in the unit. + TimeAvg uint32 } -// withRecovered turns the value recovered from panic if any into an error and -// combines it with the one pointed by orig. orig must be non-nil. -func withRecovered(orig *error) { - p := recover() - if p == nil { - return - } +// newUnitID is the default UnitIDGenFunc that generates the unique id hourly. +func newUnitID() (id uint32) { + const secsInHour = int64(time.Hour / time.Second) - var err error - switch p := p.(type) { - case error: - err = fmt.Errorf("panic: %w", p) - default: - err = fmt.Errorf("panic: recovered value of type %[1]T: %[1]v", p) - } - - *orig = errors.WithDeferred(*orig, err) + return uint32(time.Now().Unix() / secsInHour) } -// createObject creates s from conf and properly initializes it. -func createObject(conf Config) (s *statsCtx, err error) { - defer withRecovered(&err) - - s = &statsCtx{ - mu: &sync.Mutex{}, - } - if !checkInterval(conf.LimitDays) { - conf.LimitDays = 1 +func finishTxn(tx *bbolt.Tx, commit bool) (err error) { + if commit { + err = errors.Annotate(tx.Commit(), "committing: %w") + } else { + err = errors.Annotate(tx.Rollback(), "rolling back: %w") } - s.conf = &Config{} - *s.conf = conf - s.conf.limit = conf.LimitDays * 24 - if conf.UnitID == nil { - s.conf.UnitID = newUnitID - } - - if !s.dbOpen() { - return nil, fmt.Errorf("open database") - } - - id := s.conf.UnitID() - tx := s.beginTxn(true) - var udb *unitDB - if tx != nil { - log.Tracef("Deleting old units...") - firstID := id - s.conf.limit - 1 - unitDel := 0 - - err = tx.ForEach(newBucketWalker(tx, &unitDel, firstID)) - if err != nil && !errors.Is(err, errStop) { - log.Debug("stats: deleting units: %s", err) - } - - udb = s.loadUnitFromDB(tx, id) - - if unitDel != 0 { - s.commitTxn(tx) - } else { - err = tx.Rollback() - if err != nil { - log.Debug("rolling back: %s", err) - } - } - } - - u := unit{} - s.initUnit(&u, id) - if udb != nil { - deserialize(&u, udb) - } - s.current = &u - - log.Debug("stats: initialized") - - return s, nil -} - -// TODO(a.garipov): See if this is actually necessary. Looks like a rather -// bizarre solution. -const errStop errors.Error = "stop iteration" - -// newBucketWalker returns a new bucket walker that deletes old units. The -// integer that unitDelPtr points to is incremented for every successful -// deletion. If the bucket isn't deleted, f returns errStop. -func newBucketWalker( - tx *bolt.Tx, - unitDelPtr *int, - firstID uint32, -) (f func(name []byte, b *bolt.Bucket) (err error)) { - return func(name []byte, _ *bolt.Bucket) (err error) { - nameID, ok := unitNameToID(name) - if !ok || nameID < firstID { - err = tx.DeleteBucket(name) - if err != nil { - log.Debug("stats: tx.DeleteBucket: %s", err) - - return nil - } - - log.Debug("stats: deleted unit %d (name %x)", nameID, name) - - *unitDelPtr++ - - return nil - } - - return errStop - } -} - -func (s *statsCtx) Start() { - s.initWeb() - go s.periodicFlush() -} - -func checkInterval(days uint32) bool { - return days == 0 || days == 1 || days == 7 || days == 30 || days == 90 -} - -func (s *statsCtx) dbOpen() bool { - var err error - log.Tracef("db.Open...") - s.db, err = bolt.Open(s.conf.Filename, 0o644, nil) - if err != nil { - log.Error("stats: open DB: %s: %s", s.conf.Filename, err) - if err.Error() == "invalid argument" { - log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations") - } - return false - } - log.Tracef("db.Open") - return true -} - -// Atomically swap the currently active unit with a new value -// Return old value -func (s *statsCtx) swapUnit(new *unit) (u *unit) { - s.mu.Lock() - defer s.mu.Unlock() - - u = s.current - s.current = new - - return u -} - -// Get unit ID for the current hour -func newUnitID() uint32 { - return uint32(time.Now().Unix() / (60 * 60)) -} - -// Initialize a unit -func (s *statsCtx) initUnit(u *unit, id uint32) { - u.id = id - u.nResult = make([]uint64, rLast) - u.domains = make(map[string]uint64) - u.blockedDomains = make(map[string]uint64) - u.clients = make(map[string]uint64) -} - -// Open a DB transaction -func (s *statsCtx) beginTxn(wr bool) *bolt.Tx { - db := s.db - if db == nil { - return nil - } - - log.Tracef("db.Begin...") - tx, err := db.Begin(wr) - if err != nil { - log.Error("db.Begin: %s", err) - return nil - } - log.Tracef("db.Begin") - return tx -} - -func (s *statsCtx) commitTxn(tx *bolt.Tx) { - err := tx.Commit() - if err != nil { - log.Debug("tx.Commit: %s", err) - return - } - log.Tracef("tx.Commit") + return err } // bucketNameLen is the length of a bucket, a 64-bit unsigned integer. @@ -262,10 +157,10 @@ const bucketNameLen = 8 // idToUnitName converts a numerical ID into a database unit name. func idToUnitName(id uint32) (name []byte) { - name = make([]byte, bucketNameLen) - binary.BigEndian.PutUint64(name, uint64(id)) + n := [bucketNameLen]byte{} + binary.BigEndian.PutUint64(n[:], uint64(id)) - return name + return n[:] } // unitNameToID converts a database unit name into a numerical ID. ok is false @@ -278,316 +173,131 @@ func unitNameToID(name []byte) (id uint32, ok bool) { return uint32(binary.BigEndian.Uint64(name)), true } -func (s *statsCtx) ongoing() (u *unit) { - s.mu.Lock() - defer s.mu.Unlock() - - return s.current -} - -// Flush the current unit to DB and delete an old unit when a new hour is started -// If a unit must be flushed: -// . lock DB -// . atomically set a new empty unit as the current one and get the old unit -// This is important to do it inside DB lock, so the reader won't get inconsistent results. -// . write the unit to DB -// . remove the stale unit from DB -// . unlock DB -func (s *statsCtx) periodicFlush() { - for { - ptr := s.ongoing() - if ptr == nil { - break - } - - id := s.conf.UnitID() - if ptr.id == id || s.conf.limit == 0 { - time.Sleep(time.Second) - - continue - } - - tx := s.beginTxn(true) - - nu := unit{} - s.initUnit(&nu, id) - u := s.swapUnit(&nu) - udb := serialize(u) - - if tx == nil { - continue - } - - ok1 := s.flushUnitToDB(tx, u.id, udb) - ok2 := s.deleteUnit(tx, id-s.conf.limit) - if ok1 || ok2 { - s.commitTxn(tx) - } else { - _ = tx.Rollback() - } - } - - log.Tracef("periodicFlush() exited") -} - -// Delete unit's data from file -func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool { - err := tx.DeleteBucket(idToUnitName(id)) - if err != nil { - log.Tracef("stats: bolt DeleteBucket: %s", err) - - return false - } - - log.Debug("stats: deleted unit %d", id) - - return true -} - -func convertMapToSlice(m map[string]uint64, max int) []countPair { - a := []countPair{} +func convertMapToSlice(m map[string]uint64, max int) (s []countPair) { + s = make([]countPair, 0, len(m)) for k, v := range m { - pair := countPair{} - pair.Name = k - pair.Count = v - a = append(a, pair) + s = append(s, countPair{Name: k, Count: v}) } - less := func(i, j int) bool { - return a[j].Count < a[i].Count + + sort.Slice(s, func(i, j int) bool { + return s[j].Count < s[i].Count + }) + if max > len(s) { + max = len(s) } - sort.Slice(a, less) - if max > len(a) { - max = len(a) - } - return a[:max] + + return s[:max] } -func convertSliceToMap(a []countPair) map[string]uint64 { - m := map[string]uint64{} +func convertSliceToMap(a []countPair) (m map[string]uint64) { + m = map[string]uint64{} for _, it := range a { m[it.Name] = it.Count } + return m } -func serialize(u *unit) *unitDB { - udb := unitDB{} - udb.NTotal = u.nTotal - - udb.NResult = append(udb.NResult, u.nResult...) - +// serialize converts u to the *unitDB. It's safe for concurrent use. u must +// not be nil. +func (u *unit) serialize() (udb *unitDB) { + var timeAvg uint32 = 0 if u.nTotal != 0 { - udb.TimeAvg = uint32(u.timeSum / u.nTotal) + timeAvg = uint32(u.timeSum / u.nTotal) } - udb.Domains = convertMapToSlice(u.domains, maxDomains) - udb.BlockedDomains = convertMapToSlice(u.blockedDomains, maxDomains) - udb.Clients = convertMapToSlice(u.clients, maxClients) - - return &udb + return &unitDB{ + NTotal: u.nTotal, + NResult: append([]uint64{}, u.nResult...), + Domains: convertMapToSlice(u.domains, maxDomains), + BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains), + Clients: convertMapToSlice(u.clients, maxClients), + TimeAvg: timeAvg, + } } -func deserialize(u *unit, udb *unitDB) { - u.nTotal = udb.NTotal - - n := len(udb.NResult) - if n < len(u.nResult) { - n = len(u.nResult) // n = min(len(udb.NResult), len(u.nResult)) - } - for i := 1; i < n; i++ { - u.nResult[i] = udb.NResult[i] - } - - u.domains = convertSliceToMap(udb.Domains) - u.blockedDomains = convertSliceToMap(udb.BlockedDomains) - u.clients = convertSliceToMap(udb.Clients) - u.timeSum = uint64(udb.TimeAvg) * u.nTotal -} - -func (s *statsCtx) flushUnitToDB(tx *bolt.Tx, id uint32, udb *unitDB) bool { - log.Tracef("Flushing unit %d", id) - - bkt, err := tx.CreateBucketIfNotExists(idToUnitName(id)) - if err != nil { - log.Error("tx.CreateBucketIfNotExists: %s", err) - return false - } - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err = enc.Encode(udb) - if err != nil { - log.Error("gob.Encode: %s", err) - return false - } - - err = bkt.Put([]byte{0}, buf.Bytes()) - if err != nil { - log.Error("bkt.Put: %s", err) - return false - } - - return true -} - -func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB { +func loadUnitFromDB(tx *bbolt.Tx, id uint32) (udb *unitDB) { bkt := tx.Bucket(idToUnitName(id)) if bkt == nil { return nil } - // log.Tracef("Loading unit %d", id) + log.Tracef("Loading unit %d", id) var buf bytes.Buffer buf.Write(bkt.Get([]byte{0})) - dec := gob.NewDecoder(&buf) - udb := unitDB{} - err := dec.Decode(&udb) + udb = &unitDB{} + + err := gob.NewDecoder(&buf).Decode(udb) if err != nil { log.Error("gob Decode: %s", err) + return nil } - return &udb + return udb } -func convertTopSlice(a []countPair) []map[string]uint64 { - m := []map[string]uint64{} - for _, it := range a { - ent := map[string]uint64{} - ent[it.Name] = it.Count - m = append(m, ent) - } - return m -} - -func (s *statsCtx) setLimit(limitDays int) { - s.conf.limit = uint32(limitDays) * 24 - if limitDays == 0 { - s.clear() - } - - log.Debug("stats: set limit: %d", limitDays) -} - -func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) { - dc.Interval = s.conf.limit / 24 -} - -func (s *statsCtx) Close() { - u := s.swapUnit(nil) - udb := serialize(u) - tx := s.beginTxn(true) - if tx != nil { - if s.flushUnitToDB(tx, u.id, udb) { - s.commitTxn(tx) - } else { - _ = tx.Rollback() - } - } - - if s.db != nil { - log.Tracef("db.Close...") - _ = s.db.Close() - log.Tracef("db.Close") - } - - log.Debug("stats: closed") -} - -// Reset counters and clear database -func (s *statsCtx) clear() { - tx := s.beginTxn(true) - if tx != nil { - db := s.db - s.db = nil - _ = tx.Rollback() - // the active transactions can continue using database, - // but no new transactions will be opened - _ = db.Close() - log.Tracef("db.Close") - // all active transactions are now closed - } - - u := unit{} - s.initUnit(&u, s.conf.UnitID()) - _ = s.swapUnit(&u) - - err := os.Remove(s.conf.Filename) - if err != nil { - log.Error("os.Remove: %s", err) - } - - _ = s.dbOpen() - - log.Debug("stats: cleared") -} - -func (s *statsCtx) Update(e Entry) { - if s.conf.limit == 0 { +// deserealize assigns the appropriate values from udb to u. u must not be nil. +// It's safe for concurrent use. +func (u *unit) deserialize(udb *unitDB) { + if udb == nil { return } - if e.Result == 0 || - e.Result >= rLast || - e.Domain == "" || - e.Client == "" { - return - } + u.nTotal = udb.NTotal + u.nResult = make([]uint64, resultLast) + copy(u.nResult, udb.NResult) + u.domains = convertSliceToMap(udb.Domains) + u.blockedDomains = convertSliceToMap(udb.BlockedDomains) + u.clients = convertSliceToMap(udb.Clients) + u.timeSum = uint64(udb.TimeAvg) * udb.NTotal +} - clientID := e.Client - if ip := net.ParseIP(clientID); ip != nil { - clientID = ip.String() - } - - s.mu.Lock() - defer s.mu.Unlock() - - u := s.current - - u.nResult[e.Result]++ - - if e.Result == RNotFiltered { - u.domains[e.Domain]++ +// add adds new data to u. It's safe for concurrent use. +func (u *unit) add(res Result, domain, cli string, dur uint64) { + u.nResult[res]++ + if res == RNotFiltered { + u.domains[domain]++ } else { - u.blockedDomains[e.Domain]++ + u.blockedDomains[domain]++ } - u.clients[clientID]++ - u.timeSum += uint64(e.Time) + u.clients[cli]++ + u.timeSum += dur u.nTotal++ } -func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) { - tx := s.beginTxn(false) - if tx == nil { - return nil, 0 +// flushUnitToDB puts udb to the database at id. +func (udb *unitDB) flushUnitToDB(tx *bbolt.Tx, id uint32) (err error) { + log.Debug("stats: flushing unit with id %d and total of %d", id, udb.NTotal) + + bkt, err := tx.CreateBucketIfNotExists(idToUnitName(id)) + if err != nil { + return fmt.Errorf("creating bucket: %w", err) } - cur := s.ongoing() - curID := cur.id - - // Per-hour units. - units := []*unitDB{} - firstID := curID - limit + 1 - for i := firstID; i != curID; i++ { - u := s.loadUnitFromDB(tx, i) - if u == nil { - u = &unitDB{} - u.NResult = make([]uint64, rLast) - } - units = append(units, u) + buf := &bytes.Buffer{} + err = gob.NewEncoder(buf).Encode(udb) + if err != nil { + return fmt.Errorf("encoding unit: %w", err) } - _ = tx.Rollback() - - units = append(units, serialize(cur)) - - if len(units) != int(limit) { - log.Fatalf("len(units) != limit: %d %d", len(units), limit) + err = bkt.Put([]byte{0}, buf.Bytes()) + if err != nil { + return fmt.Errorf("putting unit to database: %w", err) } - return units, firstID + return nil +} + +func convertTopSlice(a []countPair) (m []map[string]uint64) { + m = make([]map[string]uint64, 0, len(a)) + for _, it := range a { + m = append(m, map[string]uint64{it.Name: it.Count}) + } + + return m } // numsGetter is a signature for statsCollector argument. @@ -597,6 +307,7 @@ type numsGetter func(u *unitDB) (num uint64) // timeUnit using ng to retrieve data. func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsGetter) (nums []uint64) { if timeUnit == Hours { + nums = make([]uint64, 0, len(units)) for _, u := range units { nums = append(nums, ng(u)) } @@ -628,16 +339,17 @@ func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsG // pairsGetter is a signature for topsCollector argument. type pairsGetter func(u *unitDB) (pairs []countPair) -// topsCollector collects statistics about highest values fro the given *unitDB +// topsCollector collects statistics about highest values from the given *unitDB // slice using pg to retrieve data. func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64 { m := map[string]uint64{} for _, u := range units { - for _, it := range pg(u) { - m[it.Name] += it.Count + for _, cp := range pg(u) { + m[cp.Name] += cp.Count } } a2 := convertMapToSlice(m, max) + return convertTopSlice(a2) } @@ -668,8 +380,21 @@ func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64 * parental-blocked These values are just the sum of data for all units. */ -func (s *statsCtx) getData() (statsResponse, bool) { - limit := s.conf.limit +func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) { + if limit == 0 { + return StatsResp{ + TimeUnits: "days", + + TopBlocked: []topAddrs{}, + TopClients: []topAddrs{}, + TopQueried: []topAddrs{}, + + BlockedFiltering: []uint64{}, + DNSQueries: []uint64{}, + ReplacedParental: []uint64{}, + ReplacedSafebrowsing: []uint64{}, + }, true + } timeUnit := Hours if limit/24 > 7 { @@ -678,7 +403,7 @@ func (s *statsCtx) getData() (statsResponse, bool) { units, firstID := s.loadUnits(limit) if units == nil { - return statsResponse{}, false + return StatsResp{}, false } dnsQueries := statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NTotal }) @@ -686,7 +411,7 @@ func (s *statsCtx) getData() (statsResponse, bool) { log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit) } - data := statsResponse{ + data := StatsResp{ DNSQueries: dnsQueries, BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), @@ -698,7 +423,7 @@ func (s *statsCtx) getData() (statsResponse, bool) { // Total counters: sum := unitDB{ - NResult: make([]uint64, rLast), + NResult: make([]uint64, resultLast), } timeN := 0 for _, u := range units { @@ -730,31 +455,3 @@ func (s *statsCtx) getData() (statsResponse, bool) { return data, true } - -func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP { - if s.conf.limit == 0 { - return nil - } - - units, _ := s.loadUnits(s.conf.limit) - if units == nil { - return nil - } - - // top clients - m := map[string]uint64{} - for _, u := range units { - for _, it := range u.Clients { - m[it.Name] += it.Count - } - } - a := convertMapToSlice(m, int(maxCount)) - d := []net.IP{} - for _, it := range a { - ip := net.ParseIP(it.Name) - if ip != nil { - d = append(d, ip) - } - } - return d -} diff --git a/internal/tools/go.mod b/internal/tools/go.mod index bb587070..74969289 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -1,34 +1,32 @@ module github.com/AdguardTeam/AdGuardHome/internal/tools -go 1.17 +go 1.18 require ( - github.com/fzipp/gocyclo v0.5.1 + github.com/fzipp/gocyclo v0.6.0 github.com/golangci/misspell v0.3.5 github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 - github.com/kisielk/errcheck v1.6.0 + github.com/kisielk/errcheck v1.6.2 github.com/kyoh86/looppointer v0.1.7 - github.com/securego/gosec/v2 v2.11.0 - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 - golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a - honnef.co/go/tools v0.3.1 + github.com/securego/gosec/v2 v2.12.0 + golang.org/x/tools v0.1.12 + honnef.co/go/tools v0.3.3 mvdan.cc/gofumpt v0.3.1 - mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b + mvdan.cc/unparam v0.0.0-20220706161116-678bad134442 ) require ( - github.com/BurntSushi/toml v1.1.0 // indirect + github.com/BurntSushi/toml v1.2.0 // indirect github.com/client9/misspell v0.3.4 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gookit/color v1.5.0 // indirect + github.com/gookit/color v1.5.1 // indirect github.com/kyoh86/nolint v0.0.1 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index 2f04e4a6..144cd0eb 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -34,9 +34,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -78,7 +77,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -94,12 +92,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns= -github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= -github.com/fzipp/gocyclo v0.5.1 h1:L66amyuYogbxl0j2U+vGqJXusPF2IkduvXLnYD5TFgw= -github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -157,7 +154,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -179,8 +175,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= -github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= +github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= @@ -222,19 +218,17 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6RlY= -github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c= +github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/looppointer v0.1.7 h1:q5sZOhFvmvQ6ZoZxvPB/Mjj2croWX7L49BBuI4XQWCM= github.com/kyoh86/looppointer v0.1.7/go.mod h1:l0cRF49N6xDPx8IuBGC/imZo8Yn1BBLJY0vzI+4fepc= @@ -244,7 +238,7 @@ github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ew github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -288,19 +282,18 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -324,14 +317,12 @@ github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI= -github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= +github.com/securego/gosec/v2 v2.12.0 h1:CQWdW7ATFpvLSohMVsajscfyHJ5rsGmEXmsNcsDNmAg= +github.com/securego/gosec/v2 v2.12.0/go.mod h1:iTpT+eKTw59bSgklBHlSnH5O2tNygHMDxfvMubA4i7I= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -356,8 +347,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -407,7 +398,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -418,11 +409,9 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 h1:pKfHvPtBtqS0+V/V9Y0cZQa2h8HJV/qSRJiGgYu+LQA= -golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e h1:7Xs2YCOpMlNqSQSmrrnhlzBXIE/bpMecZplbLePTJvE= +golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -435,7 +424,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -446,9 +434,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -488,8 +476,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -505,8 +494,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -558,12 +548,12 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME= +golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -620,7 +610,6 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -628,16 +617,14 @@ golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -743,8 +730,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -752,12 +740,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.3.1 h1:1kJlrWJLkaGXgcaeosRXViwviqjI7nkBvU2+sZW0AYc= -honnef.co/go/tools v0.3.1/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70= +honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= +honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8= mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE= -mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b h1:C8Pi6noat8BcrL9WnSRYeQ63fpkJk3hKVHtF5731kIw= -mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b/go.mod h1:WqFWCt8MGPoFSYGsQSiIORRlYVhkJsIk+n2MY6rhNbA= +mvdan.cc/unparam v0.0.0-20220706161116-678bad134442 h1:seuXWbRB1qPrS3NQnHmFKLJLtskWyueeIzmLXghMGgk= +mvdan.cc/unparam v0.0.0-20220706161116-678bad134442/go.mod h1:F/Cxw/6mVrNKqrR2YjFf5CaW0Bw4RL8RfbEf4GRggJk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index 214301bf..e41fba4a 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -11,7 +11,6 @@ import ( _ "github.com/kisielk/errcheck" _ "github.com/kyoh86/looppointer" _ "github.com/securego/gosec/v2/cmd/gosec" - _ "golang.org/x/lint/golint" _ "golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness" _ "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow" _ "honnef.co/go/tools/cmd/staticcheck" diff --git a/internal/updater/check.go b/internal/updater/check.go index ec7176b2..2bac6153 100644 --- a/internal/updater/check.go +++ b/internal/updater/check.go @@ -5,9 +5,9 @@ import ( "fmt" "io" "net/http" - "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/golibs/errors" ) @@ -17,11 +17,12 @@ const versionCheckPeriod = 8 * time.Hour // VersionInfo contains information about a new version. type VersionInfo struct { - CanAutoUpdate *bool `json:"can_autoupdate,omitempty"` - NewVersion string `json:"new_version,omitempty"` - Announcement string `json:"announcement,omitempty"` - AnnouncementURL string `json:"announcement_url,omitempty"` - SelfUpdateMinVersion string `json:"-"` + NewVersion string `json:"new_version,omitempty"` + Announcement string `json:"announcement,omitempty"` + AnnouncementURL string `json:"announcement_url,omitempty"` + // TODO(a.garipov): See if the frontend actually still cares about + // nullability. + CanAutoUpdate aghalg.NullBool `json:"can_autoupdate,omitempty"` } // MaxResponseSize is responses on server's requests maximum length in bytes. @@ -67,15 +68,13 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) { } func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { - var canAutoUpdate bool info := VersionInfo{ - CanAutoUpdate: &canAutoUpdate, + CanAutoUpdate: aghalg.NBFalse, } versionJSON := map[string]string{ - "version": "", - "announcement": "", - "announcement_url": "", - "selfupdate_min_version": "", + "version": "", + "announcement": "", + "announcement_url": "", } err := json.Unmarshal(data, &versionJSON) if err != nil { @@ -91,14 +90,9 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { info.NewVersion = versionJSON["version"] info.Announcement = versionJSON["announcement"] info.AnnouncementURL = versionJSON["announcement_url"] - info.SelfUpdateMinVersion = versionJSON["selfupdate_min_version"] packageURL, ok := u.downloadURL(versionJSON) - if ok && - info.NewVersion != u.version && - strings.TrimPrefix(u.version, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") { - canAutoUpdate = true - } + info.CanAutoUpdate = aghalg.BoolToNullBool(ok && info.NewVersion != u.version) u.newVersion = info.NewVersion u.packageURL = packageURL diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 0cc49f9e..a0672c58 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -82,8 +82,9 @@ type Config struct { func NewUpdater(conf *Config) *Updater { u := &url.URL{ Scheme: "https", - Host: "static.adguard.com", - Path: path.Join("adguardhome", conf.Channel, "version.json"), + // TODO(a.garipov): Make configurable. + Host: "static.adtidy.org", + Path: path.Join("adguardhome", conf.Channel, "version.json"), } return &Updater{ client: conf.Client, @@ -104,11 +105,19 @@ func NewUpdater(conf *Config) *Updater { } // Update performs the auto-update. -func (u *Updater) Update() error { +func (u *Updater) Update() (err error) { u.mu.Lock() defer u.mu.Unlock() - err := u.prepare() + log.Info("updater: updating") + defer func() { log.Info("updater: finished; errors: %v", err) }() + + execPath, err := os.Executable() + if err != nil { + return err + } + + err = u.prepare(filepath.Base(execPath)) if err != nil { return err } @@ -159,7 +168,8 @@ func (u *Updater) VersionCheckURL() (vcu string) { return u.versionCheckURL } -func (u *Updater) prepare() (err error) { +// prepare fills all necessary fields in Updater object. +func (u *Updater) prepare(exeName string) (err error) { u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion)) _, pkgNameOnly := filepath.Split(u.packageURL) @@ -170,17 +180,21 @@ func (u *Updater) prepare() (err error) { u.packageName = filepath.Join(u.updateDir, pkgNameOnly) u.backupDir = filepath.Join(u.workDir, "agh-backup") - exeName := "AdGuardHome" + updateExeName := "AdGuardHome" if u.goos == "windows" { - exeName = "AdGuardHome.exe" + updateExeName = "AdGuardHome.exe" } u.backupExeName = filepath.Join(u.backupDir, exeName) - u.updateExeName = filepath.Join(u.updateDir, exeName) + u.updateExeName = filepath.Join(u.updateDir, updateExeName) - log.Info("Updating from %s to %s. URL:%s", version.Version(), u.newVersion, u.packageURL) + log.Debug( + "updater: updating from %s to %s using url: %s", + version.Version(), + u.newVersion, + u.packageURL, + ) - // TODO(a.garipov): Use os.Args[0] instead? u.currentExeName = filepath.Join(u.workDir, exeName) _, err = os.Stat(u.currentExeName) if err != nil { @@ -194,7 +208,7 @@ func (u *Updater) unpack() error { var err error _, pkgNameOnly := filepath.Split(u.packageURL) - log.Debug("updater: unpacking the package") + log.Debug("updater: unpacking package") if strings.HasSuffix(pkgNameOnly, ".zip") { u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir) if err != nil { @@ -229,7 +243,7 @@ func (u *Updater) check() error { } func (u *Updater) backup() error { - log.Debug("updater: backing up the current configuration") + log.Debug("updater: backing up current configuration") _ = os.Mkdir(u.backupDir, 0o755) err := copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml")) if err != nil { @@ -252,7 +266,7 @@ func (u *Updater) replace() error { return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", u.updateDir, u.workDir, err) } - log.Debug("updater: renaming: %s -> %s", u.currentExeName, u.backupExeName) + log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName) err = os.Rename(u.currentExeName, u.backupExeName) if err != nil { return err @@ -268,7 +282,7 @@ func (u *Updater) replace() error { return err } - log.Debug("updater: renamed: %s -> %s", u.updateExeName, u.currentExeName) + log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName) return nil } @@ -297,7 +311,7 @@ func (u *Updater) downloadPackageFile(url, filename string) (err error) { return fmt.Errorf("http request failed: %w", err) } - log.Debug("updater: reading HTTP body") + log.Debug("updater: reading http body") // This use of ReadAll is now safe, because we limited body's Reader. body, err := io.ReadAll(r) if err != nil { @@ -343,7 +357,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st } if hdr.Typeflag != tar.TypeReg { - log.Debug("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag) + log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag) return "", nil } @@ -364,7 +378,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st return "", fmt.Errorf("io.Copy(): %w", err) } - log.Tracef("updater: created file %s", outputName) + log.Debug("updater: created file %q", outputName) return name, nil } @@ -440,7 +454,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) { return "", fmt.Errorf("os.Mkdir(%q): %w", outputName, err) } - log.Tracef("created directory %q", outputName) + log.Debug("updater: created directory %q", outputName) return "", nil } @@ -457,7 +471,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) { return "", fmt.Errorf("io.Copy(): %w", err) } - log.Tracef("created file %s", outputName) + log.Debug("updater: created file %q", outputName) return name, nil } @@ -516,7 +530,7 @@ func copySupportingFiles(files []string, srcdir, dstdir string) error { return err } - log.Debug("updater: copied: %q -> %q", src, dst) + log.Debug("updater: copied: %q to %q", src, dst) } return nil diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index 3a29d277..219dc087 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -10,6 +10,7 @@ import ( "strconv" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/golibs/testutil" @@ -44,28 +45,28 @@ func TestUpdateGetVersion(t *testing.T) { "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", "selfupdate_min_version": "v0.0", - "download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip", - "download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip", - "download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip", - "download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip", - "download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz", - "download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz", - "download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz", - "download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", - "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz", - "download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz", - "download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz", - "download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz", - "download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz", - "download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz", - "download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz", - "download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz", - "download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz", - "download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", - "download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz", - "download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz" + "download_windows_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip", + "download_windows_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip", + "download_darwin_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip", + "download_darwin_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip", + "download_linux_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz", + "download_linux_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz", + "download_linux_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", + "download_linux_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz", + "download_linux_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz", + "download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz", + "download_linux_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz", + "download_linux_mips": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz", + "download_linux_mipsle": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz", + "download_linux_mips64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz", + "download_linux_mips64le": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz", + "download_freebsd_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz", + "download_freebsd_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz", + "download_freebsd_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", + "download_freebsd_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz", + "download_freebsd_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz", + "download_freebsd_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz", + "download_freebsd_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz" }` l, lport := startHTTPServer(jsonData) @@ -92,10 +93,7 @@ func TestUpdateGetVersion(t *testing.T) { assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) - assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - if assert.NotNil(t, info.CanAutoUpdate) { - assert.True(t, *info.CanAutoUpdate) - } + assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate) // check cached _, err = u.VersionInfo(false) @@ -133,7 +131,7 @@ func TestUpdate(t *testing.T) { u.newVersion = "v0.103.1" u.packageURL = fakeURL.String() - require.NoError(t, u.prepare()) + require.NoError(t, u.prepare("AdGuardHome")) u.currentExeName = filepath.Join(wd, "AdGuardHome") @@ -211,7 +209,7 @@ func TestUpdateWindows(t *testing.T) { u.newVersion = "v0.103.1" u.packageURL = fakeURL.String() - require.NoError(t, u.prepare()) + require.NoError(t, u.prepare("AdGuardHome.exe")) u.currentExeName = filepath.Join(wd, "AdGuardHome.exe") @@ -262,7 +260,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) { "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", "selfupdate_min_version": "v0.0", - "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz" + "download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz" }` l, lport := startHTTPServer(jsonData) @@ -290,10 +288,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) { assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) - assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - if assert.NotNil(t, info.CanAutoUpdate) { - assert.True(t, *info.CanAutoUpdate) - } + assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate) } func TestUpdater_VersionInto_MIPS(t *testing.T) { @@ -302,7 +297,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) { "announcement": "AdGuard Home v0.103.0-beta.2 is now available!", "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases", "selfupdate_min_version": "v0.0", - "download_linux_mips_softfloat": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz" + "download_linux_mips_softfloat": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz" }` l, lport := startHTTPServer(jsonData) @@ -330,8 +325,5 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) { assert.Equal(t, "v0.103.0-beta.2", info.NewVersion) assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement) assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL) - assert.Equal(t, "v0.0", info.SelfUpdateMinVersion) - if assert.NotNil(t, info.CanAutoUpdate) { - assert.True(t, *info.CanAutoUpdate) - } + assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate) } diff --git a/internal/v1/cmd/cmd.go b/internal/v1/cmd/cmd.go index 4c4e252f..2f61509b 100644 --- a/internal/v1/cmd/cmd.go +++ b/internal/v1/cmd/cmd.go @@ -8,19 +8,19 @@ import ( "context" "io/fs" "math/rand" - "net" + "net/netip" "time" "github.com/AdguardTeam/AdGuardHome/internal/v1/websvc" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" ) // Main is the entry point of application. func Main(clientBuildFS fs.FS) { // # Initial Configuration - rand.Seed(time.Now().UnixNano()) + start := time.Now() + rand.Seed(start.UnixNano()) // TODO(a.garipov): Set up logging. @@ -31,11 +31,9 @@ func Main(clientBuildFS fs.FS) { // TODO(a.garipov): Make configurable. web := websvc.New(&websvc.Config{ - Addresses: []*netutil.IPPort{{ - IP: net.IP{127, 0, 0, 1}, - Port: 3001, - }}, - Timeout: 60 * time.Second, + Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:3001")}, + Start: start, + Timeout: 60 * time.Second, }) err := web.Start() diff --git a/internal/v1/cmd/signal.go b/internal/v1/cmd/signal.go index b9f09673..b66075f6 100644 --- a/internal/v1/cmd/signal.go +++ b/internal/v1/cmd/signal.go @@ -19,10 +19,10 @@ type signalHandler struct { // handle processes OS signals. func (h *signalHandler) handle() { - defer log.OnPanic("signalProcessor.handle") + defer log.OnPanic("signalHandler.handle") for sig := range h.signal { - log.Info("sigproc: received signal %q", sig) + log.Info("sighdlr: received signal %q", sig) if aghos.IsShutdownSignal(sig) { h.shutdown() @@ -43,16 +43,16 @@ func (h *signalHandler) shutdown() { status := statusSuccess - log.Info("sigproc: shutting down services") + log.Info("sighdlr: shutting down services") for i, service := range h.services { err := service.Shutdown(ctx) if err != nil { - log.Error("sigproc: shutting down service at index %d: %s", i, err) + log.Error("sighdlr: shutting down service at index %d: %s", i, err) status = statusError } } - log.Info("sigproc: shutting down adguard home") + log.Info("sighdlr: shutting down adguard home") os.Exit(status) } diff --git a/internal/v1/dnssvc/dnssvc.go b/internal/v1/dnssvc/dnssvc.go new file mode 100644 index 00000000..ffe5b080 --- /dev/null +++ b/internal/v1/dnssvc/dnssvc.go @@ -0,0 +1,193 @@ +// Package dnssvc contains the AdGuard Home DNS service. +// +// TODO(a.garipov): Define, if all methods of a *Service should work with a nil +// receiver. +package dnssvc + +import ( + "context" + "fmt" + "net" + "net/netip" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/v1/agh" + // TODO(a.garipov): Add a “dnsproxy proxy” package to shield us from changes + // and replacement of module dnsproxy. + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/dnsproxy/upstream" +) + +// Config is the AdGuard Home DNS service configuration structure. +// +// TODO(a.garipov): Add timeout for incoming requests. +type Config struct { + // Addresses are the addresses on which to serve plain DNS queries. + Addresses []netip.AddrPort + + // Upstreams are the DNS upstreams to use. If not set, upstreams are + // created using data from BootstrapServers, UpstreamServers, and + // UpstreamTimeout. + // + // TODO(a.garipov): Think of a better scheme. Those other three parameters + // are here only to make Config work properly. + Upstreams []upstream.Upstream + + // BootstrapServers are the addresses for bootstrapping the upstream DNS + // server addresses. + BootstrapServers []string + + // UpstreamServers are the upstream DNS server addresses to use. + UpstreamServers []string + + // UpstreamTimeout is the timeout for upstream requests. + UpstreamTimeout time.Duration +} + +// Service is the AdGuard Home DNS service. A nil *Service is a valid +// [agh.Service] that does nothing. +type Service struct { + proxy *proxy.Proxy + bootstraps []string + upstreams []string + upsTimeout time.Duration +} + +// New returns a new properly initialized *Service. If c is nil, svc is a nil +// *Service that does nothing. The fields of c must not be modified after +// calling New. +func New(c *Config) (svc *Service, err error) { + if c == nil { + return nil, nil + } + + svc = &Service{ + bootstraps: c.BootstrapServers, + upstreams: c.UpstreamServers, + upsTimeout: c.UpstreamTimeout, + } + + var upstreams []upstream.Upstream + if len(c.Upstreams) > 0 { + upstreams = c.Upstreams + } else { + upstreams, err = addressesToUpstreams( + c.UpstreamServers, + c.BootstrapServers, + c.UpstreamTimeout, + ) + if err != nil { + return nil, fmt.Errorf("converting upstreams: %w", err) + } + } + + svc.proxy = &proxy.Proxy{ + Config: proxy.Config{ + UDPListenAddr: udpAddrs(c.Addresses), + TCPListenAddr: tcpAddrs(c.Addresses), + UpstreamConfig: &proxy.UpstreamConfig{ + Upstreams: upstreams, + }, + }, + } + + err = svc.proxy.Init() + if err != nil { + return nil, fmt.Errorf("proxy: %w", err) + } + + return svc, nil +} + +// addressesToUpstreams is a wrapper around [upstream.AddressToUpstream]. It +// accepts a slice of addresses and other upstream parameters, and returns a +// slice of upstreams. +func addressesToUpstreams( + upsStrs []string, + bootstraps []string, + timeout time.Duration, +) (upstreams []upstream.Upstream, err error) { + upstreams = make([]upstream.Upstream, len(upsStrs)) + for i, upsStr := range upsStrs { + upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{ + Bootstrap: bootstraps, + Timeout: timeout, + }) + if err != nil { + return nil, fmt.Errorf("upstream at index %d: %w", i, err) + } + } + + return upstreams, nil +} + +// tcpAddrs converts []netip.AddrPort into []*net.TCPAddr. +func tcpAddrs(addrPorts []netip.AddrPort) (tcpAddrs []*net.TCPAddr) { + if addrPorts == nil { + return nil + } + + tcpAddrs = make([]*net.TCPAddr, len(addrPorts)) + for i, a := range addrPorts { + tcpAddrs[i] = net.TCPAddrFromAddrPort(a) + } + + return tcpAddrs +} + +// udpAddrs converts []netip.AddrPort into []*net.UDPAddr. +func udpAddrs(addrPorts []netip.AddrPort) (udpAddrs []*net.UDPAddr) { + if addrPorts == nil { + return nil + } + + udpAddrs = make([]*net.UDPAddr, len(addrPorts)) + for i, a := range addrPorts { + udpAddrs[i] = net.UDPAddrFromAddrPort(a) + } + + return udpAddrs +} + +// type check +var _ agh.Service = (*Service)(nil) + +// Start implements the [agh.Service] interface for *Service. svc may be nil. +// After Start exits, all DNS servers have tried to start, but there is no +// guarantee that they did. Errors from the servers are written to the log. +func (svc *Service) Start() (err error) { + if svc == nil { + return nil + } + + return svc.proxy.Start() +} + +// Shutdown implements the [agh.Service] interface for *Service. svc may be +// nil. +func (svc *Service) Shutdown(ctx context.Context) (err error) { + if svc == nil { + return nil + } + + return svc.proxy.Stop() +} + +// Config returns the current configuration of the web service. +func (svc *Service) Config() (c *Config) { + // TODO(a.garipov): Do we need to get the TCP addresses separately? + udpAddrs := svc.proxy.Addrs(proxy.ProtoUDP) + addrs := make([]netip.AddrPort, len(udpAddrs)) + for i, a := range udpAddrs { + addrs[i] = a.(*net.UDPAddr).AddrPort() + } + + c = &Config{ + Addresses: addrs, + BootstrapServers: svc.bootstraps, + UpstreamServers: svc.upstreams, + UpstreamTimeout: svc.upsTimeout, + } + + return c +} diff --git a/internal/v1/dnssvc/dnssvc_test.go b/internal/v1/dnssvc/dnssvc_test.go new file mode 100644 index 00000000..5bc3b562 --- /dev/null +++ b/internal/v1/dnssvc/dnssvc_test.go @@ -0,0 +1,89 @@ +package dnssvc_test + +import ( + "context" + "net/netip" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/AdGuardHome/internal/v1/dnssvc" + "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + aghtest.DiscardLogOutput(m) +} + +// testTimeout is the common timeout for tests. +const testTimeout = 100 * time.Millisecond + +func TestService(t *testing.T) { + const ( + bootstrapAddr = "bootstrap.example" + upstreamAddr = "upstream.example" + ) + + ups := &aghtest.UpstreamMock{ + OnAddress: func() (addr string) { + return upstreamAddr + }, + OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) { + resp = (&dns.Msg{}).SetReply(req) + + return resp, nil + }, + } + + c := &dnssvc.Config{ + Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")}, + Upstreams: []upstream.Upstream{ups}, + BootstrapServers: []string{bootstrapAddr}, + UpstreamServers: []string{upstreamAddr}, + UpstreamTimeout: testTimeout, + } + + svc, err := dnssvc.New(c) + require.NoError(t, err) + + err = svc.Start() + require.NoError(t, err) + + gotConf := svc.Config() + require.NotNil(t, gotConf) + require.Len(t, gotConf.Addresses, 1) + + addr := gotConf.Addresses[0] + + t.Run("dns", func(t *testing.T) { + req := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{{ + Name: "example.com.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + cli := &dns.Client{} + resp, _, excErr := cli.ExchangeContext(ctx, req, addr.String()) + require.NoError(t, excErr) + + assert.NotNil(t, resp) + }) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + err = svc.Shutdown(ctx) + require.NoError(t, err) +} diff --git a/internal/v1/websvc/json.go b/internal/v1/websvc/json.go new file mode 100644 index 00000000..ef84211b --- /dev/null +++ b/internal/v1/websvc/json.go @@ -0,0 +1,61 @@ +package websvc + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "github.com/AdguardTeam/golibs/log" +) + +// JSON Utilities + +// jsonTime is a time.Time that can be decoded from JSON and encoded into JSON +// according to our API conventions. +type jsonTime time.Time + +// type check +var _ json.Marshaler = jsonTime{} + +// nsecPerMsec is the number of nanoseconds in a millisecond. +const nsecPerMsec = float64(time.Millisecond / time.Nanosecond) + +// MarshalJSON implements the json.Marshaler interface for jsonTime. err is +// always nil. +func (t jsonTime) MarshalJSON() (b []byte, err error) { + msec := float64(time.Time(t).UnixNano()) / nsecPerMsec + b = strconv.AppendFloat(nil, msec, 'f', 3, 64) + + return b, nil +} + +// type check +var _ json.Unmarshaler = (*jsonTime)(nil) + +// UnmarshalJSON implements the json.Marshaler interface for *jsonTime. +func (t *jsonTime) UnmarshalJSON(b []byte) (err error) { + if t == nil { + return fmt.Errorf("json time is nil") + } + + msec, err := strconv.ParseFloat(string(b), 64) + if err != nil { + return fmt.Errorf("parsing json time: %w", err) + } + + *t = jsonTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC()) + + return nil +} + +// writeJSONResponse encodes v into w and logs any errors it encounters. r is +// used to get additional information from the request. +func writeJSONResponse(w io.Writer, r *http.Request, v any) { + err := json.NewEncoder(w).Encode(v) + if err != nil { + log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err) + } +} diff --git a/internal/v1/websvc/middleware.go b/internal/v1/websvc/middleware.go new file mode 100644 index 00000000..c87c57d5 --- /dev/null +++ b/internal/v1/websvc/middleware.go @@ -0,0 +1,16 @@ +package websvc + +import "net/http" + +// Middlewares + +// jsonMw sets the content type of the response to application/json. +func jsonMw(h http.Handler) (wrapped http.HandlerFunc) { + f := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(f) +} diff --git a/internal/v1/websvc/path.go b/internal/v1/websvc/path.go new file mode 100644 index 00000000..cfd67fd9 --- /dev/null +++ b/internal/v1/websvc/path.go @@ -0,0 +1,8 @@ +package websvc + +// Path constants +const ( + PathHealthCheck = "/health-check" + + PathV1SystemInfo = "/api/v1/system/info" +) diff --git a/internal/v1/websvc/system.go b/internal/v1/websvc/system.go new file mode 100644 index 00000000..47d0c63c --- /dev/null +++ b/internal/v1/websvc/system.go @@ -0,0 +1,35 @@ +package websvc + +import ( + "net/http" + "runtime" + + "github.com/AdguardTeam/AdGuardHome/internal/version" +) + +// System Handlers + +// RespGetV1SystemInfo describes the response of the GET /api/v1/system/info +// HTTP API. +type RespGetV1SystemInfo struct { + Arch string `json:"arch"` + Channel string `json:"channel"` + OS string `json:"os"` + NewVersion string `json:"new_version,omitempty"` + Start jsonTime `json:"start"` + Version string `json:"version"` +} + +// handleGetV1SystemInfo is the handler for the GET /api/v1/system/info HTTP +// API. +func (svc *Service) handleGetV1SystemInfo(w http.ResponseWriter, r *http.Request) { + writeJSONResponse(w, r, &RespGetV1SystemInfo{ + Arch: runtime.GOARCH, + Channel: version.Channel(), + OS: runtime.GOOS, + // TODO(a.garipov): Fill this when we have an updater. + NewVersion: "", + Start: jsonTime(svc.start), + Version: version.Version(), + }) +} diff --git a/internal/v1/websvc/system_test.go b/internal/v1/websvc/system_test.go new file mode 100644 index 00000000..49579ca5 --- /dev/null +++ b/internal/v1/websvc/system_test.go @@ -0,0 +1,36 @@ +package websvc_test + +import ( + "encoding/json" + "net/http" + "net/url" + "runtime" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/v1/websvc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestService_handleGetV1SystemInfo(t *testing.T) { + _, addr := newTestServer(t) + u := &url.URL{ + Scheme: "http", + Host: addr, + Path: websvc.PathV1SystemInfo, + } + + body := httpGet(t, u, http.StatusOK) + resp := &websvc.RespGetV1SystemInfo{} + err := json.Unmarshal(body, resp) + require.NoError(t, err) + + // TODO(a.garipov): Consider making version.Channel and version.Version + // testable and test these better. + assert.NotEmpty(t, resp.Channel) + + assert.Equal(t, resp.Arch, runtime.GOARCH) + assert.Equal(t, resp.OS, runtime.GOOS) + assert.Equal(t, testStart, time.Time(resp.Start)) +} diff --git a/internal/v1/websvc/websvc.go b/internal/v1/websvc/websvc.go index e741ff3d..bbaac005 100644 --- a/internal/v1/websvc/websvc.go +++ b/internal/v1/websvc/websvc.go @@ -10,13 +10,14 @@ import ( "io" "net" "net/http" + "net/netip" "sync" "time" "github.com/AdguardTeam/AdGuardHome/internal/v1/agh" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" + httptreemux "github.com/dimfeld/httptreemux/v5" ) // Config is the AdGuard Home web service configuration structure. @@ -26,21 +27,25 @@ type Config struct { TLS *tls.Config // Addresses are the addresses on which to serve the plain HTTP API. - Addresses []*netutil.IPPort + Addresses []netip.AddrPort // SecureAddresses are the addresses on which to serve the HTTPS API. If // SecureAddresses is not empty, TLS must not be nil. - SecureAddresses []*netutil.IPPort + SecureAddresses []netip.AddrPort + + // Start is the time of start of AdGuard Home. + Start time.Time // Timeout is the timeout for all server operations. Timeout time.Duration } -// Service is the AdGuard Home web service. A nil *Service is a valid service -// that does nothing. +// Service is the AdGuard Home web service. A nil *Service is a valid +// [agh.Service] that does nothing. type Service struct { tls *tls.Config servers []*http.Server + start time.Time timeout time.Duration } @@ -53,11 +58,11 @@ func New(c *Config) (svc *Service) { svc = &Service{ tls: c.TLS, + start: c.Start, timeout: c.Timeout, } - mux := http.NewServeMux() - mux.HandleFunc("/health-check", svc.handleGetHealthCheck) + mux := newMux(svc) for _, a := range c.Addresses { addr := a.String() @@ -91,6 +96,43 @@ func New(c *Config) (svc *Service) { return svc } +// newMux returns a new HTTP request multiplexor for the AdGuard Home web +// service. +func newMux(svc *Service) (mux *httptreemux.ContextMux) { + mux = httptreemux.NewContextMux() + + routes := []struct { + handler http.HandlerFunc + method string + path string + isJSON bool + }{{ + handler: svc.handleGetHealthCheck, + method: http.MethodGet, + path: PathHealthCheck, + isJSON: false, + }, { + handler: svc.handleGetV1SystemInfo, + method: http.MethodGet, + path: PathV1SystemInfo, + isJSON: true, + }} + + for _, r := range routes { + var h http.HandlerFunc + if r.isJSON { + // TODO(a.garipov): Consider using httptreemux's MiddlewareFunc. + h = jsonMw(r.handler) + } else { + h = r.handler + } + + mux.Handle(r.method, r.path, h) + } + + return mux +} + // Addrs returns all addresses on which this server serves the HTTP API. Addrs // must not be called until Start returns. func (svc *Service) Addrs() (addrs []string) { @@ -113,7 +155,7 @@ type unit = struct{} // type check var _ agh.Service = (*Service)(nil) -// Start implements the agh.Service interface for *Service. svc may be nil. +// Start implements the [agh.Service] interface for *Service. svc may be nil. // After Start exits, all HTTP servers have tried to start, possibly failing and // writing error messages to the log. func (svc *Service) Start() (err error) { @@ -163,7 +205,8 @@ func serve(srv *http.Server, wg *sync.WaitGroup) { } } -// Shutdown implements the agh.Service interface for *Service. svc may be nil. +// Shutdown implements the [agh.Service] interface for *Service. svc may be +// nil. func (svc *Service) Shutdown(ctx context.Context) (err error) { if svc == nil { return nil diff --git a/internal/v1/websvc/websvc_test.go b/internal/v1/websvc/websvc_test.go index 01b892cd..de4a9f5d 100644 --- a/internal/v1/websvc/websvc_test.go +++ b/internal/v1/websvc/websvc_test.go @@ -3,14 +3,13 @@ package websvc_test import ( "context" "io" - "net" "net/http" + "net/netip" "net/url" "testing" "time" "github.com/AdguardTeam/AdGuardHome/internal/v1/websvc" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,18 +17,26 @@ import ( const testTimeout = 1 * time.Second -func TestService_Start_getHealthCheck(t *testing.T) { +// testStart is the server start value for tests. +var testStart = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + +// newTestServer creates and starts a new web service instance as well as its +// sole address. It also registers a cleanup procedure, which shuts the +// instance down. +// +// TODO(a.garipov): Use svc or remove it. +func newTestServer(t testing.TB) (svc *websvc.Service, addr string) { + t.Helper() + c := &websvc.Config{ - TLS: nil, - Addresses: []*netutil.IPPort{{ - IP: net.IP{127, 0, 0, 1}, - Port: 0, - }}, + TLS: nil, + Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")}, SecureAddresses: nil, Timeout: testTimeout, + Start: testStart, } - svc := websvc.New(c) + svc = websvc.New(c) err := svc.Start() require.NoError(t, err) @@ -44,26 +51,43 @@ func TestService_Start_getHealthCheck(t *testing.T) { addrs := svc.Addrs() require.Len(t, addrs, 1) - u := &url.URL{ - Scheme: "http", - Host: addrs[0], - Path: "/health-check", - } + return svc, addrs[0] +} + +// httpGet is a helper that performs an HTTP GET request and returns the body of +// the response as well as checks that the status code is correct. +// +// TODO(a.garipov): Add helpers for other methods. +func httpGet(t testing.TB, u *url.URL, wantCode int) (body []byte) { + t.Helper() + req, err := http.NewRequest(http.MethodGet, u.String(), nil) - require.NoError(t, err) + require.NoErrorf(t, err, "creating req") httpCli := &http.Client{ Timeout: testTimeout, } resp, err := httpCli.Do(req) - require.NoError(t, err) + require.NoErrorf(t, err, "performing req") + require.Equal(t, wantCode, resp.StatusCode) testutil.CleanupAndRequireSuccess(t, resp.Body.Close) - assert.Equal(t, http.StatusOK, resp.StatusCode) + body, err = io.ReadAll(resp.Body) + require.NoErrorf(t, err, "reading body") - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) + return body +} + +func TestService_Start_getHealthCheck(t *testing.T) { + _, addr := newTestServer(t) + u := &url.URL{ + Scheme: "http", + Host: addr, + Path: websvc.PathHealthCheck, + } + + body := httpGet(t, u, http.StatusOK) assert.Equal(t, []byte("OK"), body) } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 19193151..ad57d807 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1409,7 +1409,6 @@ 'required': - 'enabled' - 'id' - - 'last_updated' - 'name' - 'rules_count' - 'url' @@ -1447,6 +1446,10 @@ 'type': 'array' 'items': '$ref': '#/components/schemas/Filter' + 'whitelist_filters': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/Filter' 'user_rules': 'type': 'array' 'items': @@ -1464,18 +1467,28 @@ 'description': 'Filtering URL settings' 'properties': 'data': - 'properties': - 'enabled': - 'type': 'boolean' - 'name': - 'type': 'string' - 'url': - 'type': 'string' - 'type': 'object' + '$ref': '#/components/schemas/FilterSetUrlData' 'url': 'type': 'string' 'whitelist': 'type': 'boolean' + 'FilterSetUrlData': + 'type': 'object' + 'description': 'Filter update data' + 'required': + - 'enabled' + - 'name' + - 'url' + 'properties': + 'enabled': + 'type': 'boolean' + 'name': + 'example': 'AdGuard Simplified Domain Names filter' + 'type': 'string' + 'url': + 'type': 'string' + 'example': > + https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt 'FilterRefreshRequest': 'type': 'object' 'description': 'Refresh Filters request data' @@ -1873,6 +1886,8 @@ 'description': 'Previously added URL containing filtering rules' 'type': 'string' 'example': 'https://filters.adtidy.org/windows/filters/15.txt' + 'whitelist': + 'type': 'boolean' 'QueryLogItem': 'type': 'object' 'description': 'Query log item' diff --git a/openapi/v1.yaml b/openapi/v1.yaml index 30c318bc..77eb1a09 100644 --- a/openapi/v1.yaml +++ b/openapi/v1.yaml @@ -144,6 +144,13 @@ '/health-check': 'get': 'operationId': 'HealthCheck' + 'responses': + '200': + 'description': > + An OK response. + 'content': + 'text/plain': + 'example': 'OK' 'servers': - 'url': '/' 'summary': 'Check if the server is up.' @@ -874,6 +881,26 @@ 'tags': - 'settings' + '/settings/http': + 'patch': + 'operationId': 'PatchV1SettingsHttp' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsHttpReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsHttpResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update web interface settings.' + 'tags': + - 'settings' + '/settings/log': 'patch': 'operationId': 'PatchV1SettingsLog' @@ -1209,6 +1236,13 @@ '$ref': '#/components/schemas/PatchV1SettingsDnsReq' 'required': true + 'PatchV1SettingsHttpReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsHttpReq' + 'required': true + 'PatchV1SettingsLogReq': 'content': 'application/json': @@ -1604,6 +1638,14 @@ 'description': > A successful response to a `PATCH /api/v1/settings/dns` request. + 'PatchV1SettingsHttpResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsHttpResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/http` request. + 'PatchV1SettingsLogResp': 'content': 'application/json': @@ -2229,6 +2271,9 @@ - 'description': > DNS server settings. 'example': + 'addresses': + - '127.0.0.1:53' + - '192.168.1.1:53' 'blocking_mode': 'default' 'bootstrap_servers': - '9.9.9.10' @@ -2244,7 +2289,9 @@ 'upstream_servers': - '1.1.1.1' - '8.8.8.8' + 'upstream_timeout': '1s' 'required': + - 'addresses' - 'blocking_mode' - 'bootstrap_servers' - 'cache_size' @@ -2256,6 +2303,7 @@ - 'rate_limit' - 'upstream_mode' - 'upstream_servers' + - 'upstream_timeout' 'DnsSettingsPatch': 'description': > @@ -2265,6 +2313,13 @@ 'upstream_servers': - '1.1.1.1' 'properties': + 'addresses': + 'description': > + Addresses on which to serve plain DNS, in ip:port format. Empty + array disables plain DNS. + 'items': + 'type': 'string' + 'type': 'array' 'blocking_ipv4': 'description': > IPv4 address to respond with when `blocking_mode` is `custom_ip`. @@ -2340,6 +2395,10 @@ 'items': '$ref': '#/components/schemas/UpstreamServerAddr' 'type': 'array' + 'upstream_timeout': + 'description': > + Upstream request timeout, as a human readable duration. + 'type': 'string' 'type': 'object' 'DnsType': @@ -3038,6 +3097,8 @@ '$ref': '#/components/schemas/DhcpSettings' 'dns': '$ref': '#/components/schemas/DnsSettings' + 'http': + '$ref': '#/components/schemas/HttpSettings' 'log': '$ref': '#/components/schemas/LogSettings' 'protection': @@ -3049,6 +3110,7 @@ 'required': - 'dhcp' - 'dns' + - 'http' - 'log' - 'protection' - 'stats' @@ -3393,11 +3455,17 @@ 'description': > Information about the AdGuard Home server. 'example': + 'arch': 'amd64' 'channel': 'release' - 'new_version': 'v0.106.1' + 'new_version': 'v0.108.1' + 'os': 'linux' 'start': 1614345496000 - 'version': 'v0.106.0' + 'version': 'v0.108.0' 'properties': + 'arch': + 'description': > + CPU architecture. + 'type': 'string' 'channel': '$ref': '#/components/schemas/Channel' 'new_version': @@ -3405,6 +3473,10 @@ New available version of AdGuard Home to which the server can be updated, if any. If there are none, this field is absent. 'type': 'string' + 'os': + 'description': > + Operating system type. + 'type': 'string' 'start': 'description': > Unix time at which AdGuard Home started working, in milliseconds. @@ -3415,11 +3487,60 @@ Current AdGuard Home version. 'type': 'string' 'required': + - 'arch' - 'channel' + - 'os' - 'start' - 'version' 'type': 'object' + 'HttpSettings': + 'allOf': + - '$ref': '#/components/schemas/HttpSettingsPatch' + - 'description': > + HTTP interface server settings. + + **TODO(a.garipov): Finish, split from TLS settings.** + 'example': + 'addresses': + - '127.0.0.1:80' + - '192.168.1.1:80' + 'secure_addresses': + - '127.0.0.1:443' + - '192.168.1.1:443' + 'force_https': true + 'required': + - 'addresses' + - 'secure_addresses' + - 'force_https' + + 'HttpSettingsPatch': + 'description': > + HTTP server settings update object. + 'example': + 'force_https': false + 'properties': + 'addresses': + 'description': > + Addresses on which to serve the plain-HTTP web interface and API, in + ip:port format. Empty array disables the web interface over plain + HTTP. + 'items': + 'type': 'string' + 'type': 'array' + 'force_https': + 'description': > + If `true`, enabled the HTTP-to-HTTPS redirect. + 'type': 'boolean' + 'secure_addresses': + 'description': > + Addresses on which to serve the HTTPS web interface and API, in + ip:port format. Empty array disables the web interface over HTTPS. + 'items': + 'type': 'string' + 'type': 'array' + 'type': 'object' + 'InternalServerErrorResp': 'example': 'code': 'RNT000' @@ -3713,6 +3834,12 @@ 'PatchV1SettingsDnsResp': '$ref': '#/components/schemas/DnsSettings' + 'PatchV1SettingsHttpReq': + '$ref': '#/components/schemas/HttpSettingsPatch' + + 'PatchV1SettingsHttpResp': + '$ref': '#/components/schemas/HttpSettings' + 'PatchV1SettingsLogReq': '$ref': '#/components/schemas/LogSettingsPatch' @@ -4278,7 +4405,7 @@ Validatable TLS settings. 'example': 'certificate_path': '/etc/ssl/example.com.cert' - 'port_dns_over_quic': 784 + 'port_dns_over_quic': 853 'port_dns_over_tls': 853 'port_https': 443 'private_key_path': '/etc/ssl/example.com.key' @@ -4300,7 +4427,7 @@ sent. 'type': 'string' 'port_dns_over_quic': - 'default': 784 + 'default': 853 'description': > The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. 'format': 'int64' @@ -4738,15 +4865,13 @@ 'example': 'certificate_path': '/etc/ssl/example.com.cert' 'enabled': true - 'force_https': true - 'port_dns_over_quic': 784 + 'port_dns_over_quic': 853 'port_dns_over_tls': 853 'port_https': 443 'private_key_path': '/etc/ssl/example.com.key' 'server_name': 'dns.example.com' 'required': - 'enabled' - - 'force_https' - 'port_dns_over_quic' - 'port_dns_over_tls' - 'port_https' @@ -4781,12 +4906,8 @@ over HTTPS, and the DNS server will listen requests over DNS-over-TLS and other protocols. 'type': 'boolean' - 'force_https': - 'description': > - If `true`, enabled the HTTP-to-HTTPS redirect. - 'type': 'boolean' 'port_dns_over_quic': - 'default': 784 + 'default': 853 'description': > The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. 'format': 'int64' @@ -4876,13 +4997,12 @@ * `94.140.14.140`: plain DNS-over-UDP. - * `tls://dns-unfiltered.adguard.com`: encrypted DNS-over-TLS. + * `tls://unfiltered.adguard-dns.com`: encrypted DNS-over-TLS. - * `https://dns-unfiltered.adguard.com/dns-query`: encrypted + * `https://unfiltered.adguard-dns.com/dns-query`: encrypted DNS-over-HTTPS. - * `quic://dns-unfiltered.adguard.com:784`: encrypted DNS-over-QUIC - (experimental). + * `quic://unfiltered.adguard-dns.com`: encrypted DNS-over-QUIC. * `tcp://94.140.14.140`: plain DNS-over-TCP. diff --git a/scripts/install.sh b/scripts/install.sh index 8cad4609..5931fd3c 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -381,7 +381,7 @@ configure() { check_out_dir pkg_name="AdGuardHome_${os}_${cpu}.${pkg_ext}" - url="https://static.adguard.com/adguardhome/${channel}/${pkg_name}" + url="https://static.adtidy.org/adguardhome/${channel}/${pkg_name}" agh_dir="${out_dir}/AdGuardHome" readonly pkg_name url agh_dir diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile index f5a543a2..a940a155 100644 --- a/scripts/make/Dockerfile +++ b/scripts/make/Dockerfile @@ -1,6 +1,6 @@ # A docker file for scripts/make/build-docker.sh. -FROM alpine:3.13 +FROM alpine:3.16 ARG BUILD_DATE ARG VERSION @@ -21,8 +21,7 @@ LABEL\ org.opencontainers.image.version=$VERSION # Update certificates. -RUN apk --no-cache --update add ca-certificates libcap tzdata && \ - rm -rf /var/cache/apk/* && \ +RUN apk --no-cache add ca-certificates libcap tzdata && \ mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \ chown -R nobody: /opt/adguardhome @@ -49,6 +48,9 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome # 5443 : TCP, UDP : DNSCrypt (alt) # 6060 : TCP : HTTP (pprof) # 8853 : UDP : DNS-over-QUIC (experimental) +# +# TODO(a.garipov): Remove the old, non-standard 784 and 8853 ports for +# DNS-over-QUIC in a future release. EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\ 853/tcp 853/udp 3000/tcp 3000/udp 3001/tcp 3001/udp 5443/tcp\ 5443/udp 6060/tcp 8853/udp diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh index 844d7da2..917e7771 100644 --- a/scripts/make/build-docker.sh +++ b/scripts/make/build-docker.sh @@ -50,6 +50,11 @@ readonly docker_image_name # Set DOCKER_OUTPUT to 'type=image,name=adguard/adguard-home,push=true' if you # want (and are allowed) to push to DockerHub. +# +# If you want to inspect the resulting image using commands like "docker image +# ls", change type to docker and also set docker_platforms to a single platform. +# +# See https://github.com/docker/buildx/issues/166. docker_output="${DOCKER_OUTPUT:-type=image,name=${docker_image_name},push=false}" readonly docker_output diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 36fc98f7..1c8cb9c0 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -109,17 +109,17 @@ log "checking tools" # Make sure we fail gracefully if one of the tools we need is missing. Use # alternatives when available. -sha256sum_cmd='sha256sum' -for tool in gpg gzip sed "$sha256sum_cmd" snapcraft tar zip +use_shasum='0' +for tool in gpg gzip sed sha256sum snapcraft tar zip do if ! command -v "$tool" > /dev/null then - if [ "$tool" = "$sha256sum_cmd" ] && command -v 'shasum' > /dev/null + if [ "$tool" = 'sha256sum' ] && command -v 'shasum' > /dev/null then - # macOS doesn't have sha256sum installed by default, but - # it does have shasum. + # macOS doesn't have sha256sum installed by default, but it does + # have shasum. log 'replacing sha256sum with shasum -a 256' - sha256sum_cmd='shasum -a 256' + use_shasum='1' else log "pieces don't fit, '$tool' not found" @@ -127,7 +127,7 @@ do fi fi done -readonly sha256sum_cmd +readonly use_shasum # Data section. Arrange data into space-separated tables for read -r to read. # Use a hyphen for missing values. @@ -332,15 +332,40 @@ log "$build_archive" log "calculating checksums" +# calculate_checksums uses the previously detected SHA-256 tool to calculate +# checksums. Do not use find with -exec, since shasum requires arguments. +calculate_checksums() { + if [ "$use_shasum" -eq '0' ] + then + sha256sum "$@" + else + shasum -a 256 "$@" + fi +} + # Calculate the checksums of the files in a subshell with a different working # directory. Don't use ls, because files matching one of the patterns may be # absent, which will make ls return with a non-zero status code. +# +# TODO(a.garipov): Consider calculating these as the build goes. ( + set +f + cd "./${dist}" - find . ! -name . -prune \( -name '*.tar.gz' -o -name '*.zip' \)\ - -exec "$sha256sum_cmd" {} +\ - > ./checksums.txt + : > ./checksums.txt + + for archive in ./*.zip ./*.tar.gz + do + # Make sure that we don't try to calculate a checksum for a glob pattern + # that matched no files. + if [ ! -f "$archive" ] + then + continue + fi + + calculate_checksums "$archive" >> ./checksums.txt + done ) log "writing versions" @@ -349,7 +374,7 @@ echo "version=$version" > "./${dist}/version.txt" # Create the version.json file. -version_download_url="https://static.adguard.com/adguardhome/${channel}" +version_download_url="https://static.adtidy.org/adguardhome/${channel}" version_json="./${dist}/version.json" readonly version_download_url version_json @@ -363,6 +388,7 @@ else fi readonly announcement_url +# TODO(a.garipov): Remove "selfupdate_min_version" in future versions. rm -f "$version_json" echo "{ \"version\": \"${version}\", diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index df2f297a..b67b8eaf 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -52,7 +52,7 @@ trap not_found EXIT go_version="$( "${GO:-go}" version )" readonly go_version -go_min_version='go1.17' +go_min_version='go1.18' go_version_msg=" warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}). if you have the version installed, please set the GO environment variable. @@ -154,7 +154,7 @@ underscores() { fi } -# TODO(a.garipov): Add an analyser to look for `fallthrough`, `goto`, and `new`? +# TODO(a.garipov): Add an analyzer to look for `fallthrough`, `goto`, and `new`? @@ -212,7 +212,10 @@ exit_on_output underscores exit_on_output gofumpt --extra -e -l . -golint --set_exit_status ./... +# TODO(a.garipov): golint is deprecated, and seems to cause more and more +# issues with each release. Find a suitable replacement. +# +# golint --set_exit_status ./... "$GO" vet ./... diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh index e0ff237d..1830c552 100644 --- a/scripts/make/go-tools.sh +++ b/scripts/make/go-tools.sh @@ -42,7 +42,6 @@ env\ github.com/kisielk/errcheck\ github.com/kyoh86/looppointer/cmd/looppointer\ github.com/securego/gosec/v2/cmd/gosec\ - golang.org/x/lint/golint\ golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness\ golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow\ honnef.co/go/tools/cmd/staticcheck\ diff --git a/scripts/querylog/test/querylog.json b/scripts/querylog/test/querylog.json index a8fa71f4..543917f8 100644 --- a/scripts/querylog/test/querylog.json +++ b/scripts/querylog/test/querylog.json @@ -1,5 +1,5 @@ -{"IP":"192.168.0.0","T":"2020-08-31T16:43:37.724457416+03:00","QH":"mtalk.google.com","QT":"A","QC":"IN","CP":"","Answer":"rm+BgAABAAIAAAAABW10YWxrBmdvb2dsZQNjb20AAAEAAcAMAAUAAQAAnwUAEQxtb2JpbGUtZ3RhbGsBbMASwC4AAQABAAAAWQAEjvobvA==","Result":{},"Elapsed":48051030,"Upstream":"tls://dns-unfiltered.adguard.com:853"} +{"IP":"192.168.0.0","T":"2020-08-31T16:43:37.724457416+03:00","QH":"mtalk.google.com","QT":"A","QC":"IN","CP":"","Answer":"rm+BgAABAAIAAAAABW10YWxrBmdvb2dsZQNjb20AAAEAAcAMAAUAAQAAnwUAEQxtb2JpbGUtZ3RhbGsBbMASwC4AAQABAAAAWQAEjvobvA==","Result":{},"Elapsed":48051030,"Upstream":"tls://unfiltered.adguard-dns.com:853"} {"IP":"127.0.0.1","T":"2020-09-09T13:56:35.532956+03:00","QH":"example.org","QT":"AAAA","QC":"IN","CP":"","Answer":"mrOBgAABAAEAAAAAB2V4YW1wbGUDb3JnAAAcAAHADAAcAAEAAKjAABAmBigAAiAAAQJIGJMlyBlG","Result":{},"Elapsed":132164793,"Upstream":"https://dns10.quad9.net:443/dns-query"} {"IP":"127.0.0.1","T":"2020-09-09T13:56:54.255453+03:00","QH":"ad.doubleclick.net","QT":"A","QC":"IN","CP":"","Answer":"wqmBgAABAAIAAAAAAmFkC2RvdWJsZWNsaWNrA25ldAAAAQABwAwABQABAACTawAJBGRhcnQBbMAPwDAAAQABAAAA5gAErNkQhg==","Result":{},"Elapsed":48131793,"Upstream":"https://dns10.quad9.net:443/dns-query"} {"IP":"127.0.0.1","T":"2020-09-09T13:57:07.495948+03:00","QH":"ad.doubleclick.net","QT":"A","QC":"IN","CP":"","Answer":"JP2BhQABAAAAAAAAAmFkC2RvdWJsZWNsaWNrA25ldAAAAQAB","Result":{"IsFiltered":true,"Reason":3,"Rule":"||ad.doubleclick.net^","FilterID":1},"Elapsed":369806} -{"IP":"192.168.0.15","T":"2020-01-17T17:39:40.306375885+03:00","QH":"push.apple.com","QT":"TXT","QC":"IN","Answer":"8AWBgAABAAEAAAABBHB1c2gFYXBwbGUDY29tAAAQAAHADAAQAAEAABOsAAkIY291bnQ9NTAAACkFrAAAAAAAQAAMADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","Result":{},"Elapsed":30271893,"Upstream":"https://cloudflare-dns.com:443/dns-query"} \ No newline at end of file +{"IP":"192.168.0.15","T":"2020-01-17T17:39:40.306375885+03:00","QH":"push.apple.com","QT":"TXT","QC":"IN","Answer":"8AWBgAABAAEAAAABBHB1c2gFYXBwbGUDY29tAAAQAAHADAAQAAEAABOsAAkIY291bnQ9NTAAACkFrAAAAAAAQAAMADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","Result":{},"Elapsed":30271893,"Upstream":"https://cloudflare-dns.com:443/dns-query"} diff --git a/scripts/snap/snap.tmpl.yaml b/scripts/snap/snap.tmpl.yaml index 51b094de..8bff5c53 100644 --- a/scripts/snap/snap.tmpl.yaml +++ b/scripts/snap/snap.tmpl.yaml @@ -1,7 +1,7 @@ # The %VARIABLES% are be replaced by actual values by the build script. 'name': 'adguard-home' -'base': 'core20' +'base': 'core22' 'version': '%VERSION%' 'summary': Network-wide ads & trackers blocking DNS server 'description': | diff --git a/scripts/translations/download.js b/scripts/translations/download.js index c511b56f..0748fff8 100644 --- a/scripts/translations/download.js +++ b/scripts/translations/download.js @@ -107,7 +107,7 @@ const download = async () => { // Don't request the Crowdin API too aggressively to prevent spurious // 400 errors. - await sleep(200); + await sleep(300); } Promise