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/stale.yml b/.github/stale.yml index d242832f..6ed9a7df 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -8,6 +8,7 @@ - 'documentation' - 'enhancement' - 'feature request' +- 'help wanted' - 'localization' - 'needs investigation' - 'recurrent' 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/AGHTechDoc.md b/AGHTechDoc.md index 4758a0da..8f69e5bd 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -1355,6 +1355,19 @@ Internally, all supported services are stored as a map: service name -> list of rules +### API: Get blocked services list of available services + +Request: + + GET /control/blocked_services/services + +Response: + + 200 OK + + [ "name1", ... ] + + ### API: Get blocked services list Request: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2976e276..ca77782c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,19 +12,227 @@ and this project adheres to ## [Unreleased] +### Security + +- Weaker cipher suites that use the CBC (cipher block chaining) mode of + operation have been disabled ([#2993]). + ### Added +- A new HTTP API, `GET /control/blocked_services/services`, that lists all + available blocked services ([#4535]). + +### 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 +[#4535]: https://github.com/AdguardTeam/AdGuardHome/issues/4535 +[#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 + +- Bilibili service blocking ([#4795]). + +### Changed + +- DNS-over-QUIC connections now use keepalive. + +### 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 + through the new `dns.private_networks` property in the configuration file + ([#3142]). +- EDNS Client-Subnet information in the request details section of a query log + record ([#3978]). +- Support for hostnames for plain UDP upstream servers using the `udp://` scheme + ([#4166]). +- Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is + installed as a service ([#4213]). + +### 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 [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 + ([#3597]). +- The TTL of responses served from the optimistic cache is now lowered to 10 + seconds. - Domain-specific private reverse DNS upstream servers are now validated to allow only `*.in-addr.arpa` and `*.ip6.arpa` domains pointing to locally-served networks ([#3381]). **Note:** If you already have invalid - entires in your configuration, consider removing them manually, since they + entries in your configuration, consider removing them manually, since they essentially had no effect. - Response filtering is now performed using the record types of the answer section of messages as opposed to the type of the question ([#4238]). @@ -33,16 +241,44 @@ and this project adheres to of the commit from which the binary was built ([#4221]). This should simplify reproducible builds for package maintainers and those who compile their own AdGuard Home. -- The setting `local_domain_name` is now in the `dhcp` block in the +- The property `local_domain_name` is now in the `dhcp` object in the configuration file to avoid confusion ([#3367]). -- The `dns.bogus_nxdomain` configuration file parameter now supports CIDR +- The `dns.bogus_nxdomain` property in the configuration file now supports CIDR notation alongside IP addresses ([#1730]). #### Configuration Changes -In this release, the schema version has changed from 12 to 13. +In this release, the schema version has changed from 12 to 14. -- Parameter `local_domain_name`, which in schema versions 12 and earlier used to +- Object `clients`, which in schema versions 13 and earlier was an array of + actual persistent clients, is now consist of `persistent` and + `runtime_sources` properties: + + ```yaml + # BEFORE: + 'clients': + - name: client-name + # … + + # AFTER: + 'clients': + 'persistent': + - name: client-name + # … + 'runtime_sources': + whois: true + arp: true + rdns: true + dhcp: true + hosts: true + ``` + + The value for `clients.runtime_sources.rdns` field is taken from + `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_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: ```yaml @@ -57,12 +293,134 @@ In this release, the schema version has changed from 12 to 13. 'local_domain_name': 'lan' ``` - To rollback this change, move the parameter back into `dns` and change the - `schema_version` back to `12`. + To rollback this change, move the property back into the `dns` object and + change the `schema_version` back to `12`. ### Deprecated -- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build. +- 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. + +### Fixed + +- 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 +[#3020]: https://github.com/AdguardTeam/AdGuardHome/issues/3020 +[#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142 +[#3157]: https://github.com/AdguardTeam/AdGuardHome/issues/3157 +[#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367 +[#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381 +[#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503 +[#3597]: https://github.com/AdguardTeam/AdGuardHome/issues/3597 +[#3978]: https://github.com/AdguardTeam/AdGuardHome/issues/3978 +[#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166 +[#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 + +[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 + + + +## [v0.107.6] - 2022-04-13 + +See also the [v0.107.6 GitHub milestone][ms-v0.107.6]. + +### Security + +- `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] Go vulnerabilities. + +### Added + +- Support for SVCB/HTTPS parameter `dohpath` in filtering rules with + the `dnsrewrite` modifier according to the [RFC draft][dns-draft-02] + ([#4463]). + +### Changed + +- Filtering rules with the `dnsrewrite` modifier that create SVCB or HTTPS + responses should use `ech` instead of `echconfig` to conform with the [latest + drafts][svcb-draft-08]. + +### Deprecated + +- SVCB/HTTPS parameter name `echconfig` in filtering rules with the `dnsrewrite` + modifier. Use `ech` instead. v0.109.0 will remove support for the outdated + name `echconfig`. +- Obsolete `--no-mem-optimization` option ([#4437]). v0.109.0 will remove the + flag completely. + +### Fixed + +- I/O timeout errors when checking for the presence of another DHCP server. +- Network interfaces being incorrectly labeled as down during installation. +- Rules for blocking the QQ service ([#3717]). + +### Removed + +- Go 1.16 support, since that branch of the Go compiler has reached end of life + and doesn't receive security updates anymore. + +[#3717]: https://github.com/AdguardTeam/AdGuardHome/issues/3717 +[#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437 +[#4463]: https://github.com/AdguardTeam/AdGuardHome/issues/4463 + +[CVE-2022-24675]: https://www.cvedetails.com/cve/CVE-2022-24675 +[CVE-2022-27536]: https://www.cvedetails.com/cve/CVE-2022-27536 +[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 + + + +## [v0.107.5] - 2022-03-04 + +This is a security update. There is no GitHub milestone, since no GitHub issues +were resolved. + +### Security + +- Go version was updated to prevent the possibility of exploiting the + [CVE-2022-24921] Go vulnerability. + +[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921 + + + +## [v0.107.4] - 2022-03-01 + +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] Go vulnerabilities. ### Fixed @@ -71,38 +429,13 @@ In this release, the schema version has changed from 12 to 13. - Unnecessarily complex hosts-related logic leading to infinite recursion in some cases ([#4216]). -### Removed - -- Go 1.16 support. - -### Security - -- Enforced password strength policy ([3503]). -- Weaker cipher suites that use the CBC (cipher block chaining) mode of - operation have been disabled ([#2993]). - -[#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730 -[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993 -[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057 -[#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367 -[#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381 -[#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216 -[#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221 -[#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238 [#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254 -[repr]: https://reproducible-builds.org/docs/source-date-epoch/ - - - - +[CVE-2022-23772]: https://www.cvedetails.com/cve/CVE-2022-23772 +[CVE-2022-23773]: https://www.cvedetails.com/cve/CVE-2022-23773 +[CVE-2022-23806]: https://www.cvedetails.com/cve/CVE-2022-23806 +[ms-v0.107.4]: https://github.com/AdguardTeam/AdGuardHome/milestone/41?closed=1 @@ -112,7 +445,7 @@ See also the [v0.107.3 GitHub milestone][ms-v0.107.3]. ### Added -- Support for a `$dnsrewrite` modifier with an empty `NOERROR` response +- Support for a `dnsrewrite` modifier with an empty `NOERROR` response ([#4133]). ### Fixed @@ -188,20 +521,20 @@ 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]). - Optimistic cache ([#2145]). -- New possible value of `6h` for `querylog_interval` setting ([#2504]). +- New possible value of `6h` for `querylog_interval` property ([#2504]). - Blocking access using ClientIDs ([#2624], [#3162]). - `source` directives support in `/etc/network/interfaces` on Linux ([#3257]). -- RFC 9000 support in DNS-over-QUIC. +- [RFC 9000][rfc-9000] support in QUIC. - Completely disabling statistics by setting the statistics interval to zero ([#2141]). - The ability to completely purge DHCP leases ([#1691]). - Settable timeouts for querying the upstream servers ([#2280]). -- Configuration file parameters to change group and user ID on startup on Unix +- Configuration file properties to change group and user ID on startup on Unix ([#2763]). - Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225], [#3226]). @@ -228,7 +561,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0]. - Better error message for ED25519 private keys, which are not widely supported ([#3737]). - Cache now follows RFC more closely for negative answers ([#3707]). -- `$dnsrewrite` rules and other DNS rewrites will now be applied even when the +- `dnsrewrite` rules and other DNS rewrites will now be applied even when the protection is disabled ([#1558]). - DHCP gateway address, subnet mask, IP address range, and leases validations ([#3529]). @@ -244,22 +577,22 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0]. proxy ([#2799]). - Clients who are blocked by access settings now receive a `REFUSED` response when a protocol other than DNS-over-UDP and DNSCrypt is used. -- `querylog_interval` setting is now formatted in hours. +- `dns.querylog_interval` property is now formatted in hours. - Query log search now supports internationalized domains ([#3012]). - Internationalized domains are now shown decoded in the query log with the original encoded version shown in request details ([#3013]). - When /etc/hosts-type rules have several IPs for one host, all IPs are now returned instead of only the first one ([#1381]). -- The setting `rlimit_nofile` is now in the `os` block of the configuration - file, together with the new `group` and `user` settings ([#2763]). +- Property `rlimit_nofile` is now in the `os` object of the configuration + file, together with the new `group` and `user` properties ([#2763]). - Permissions on filter files are now `0o644` instead of `0o600` ([#3198]). #### Configuration Changes In this release, the schema version has changed from 10 to 12. -- Parameter `dns.querylog_interval`, which in schema versions 11 and earlier - used to be an integer number of days, is now a string with a human-readable +- Property `dns.querylog_interval`, which in schema versions 11 and earlier used + to be an integer number of days, is now a string with a human-readable duration: ```yaml @@ -274,10 +607,10 @@ In this release, the schema version has changed from 10 to 12. 'querylog_interval': '2160h' ``` - To rollback this change, convert the parameter back into days and change the + To rollback this change, convert the property back into days and change the `schema_version` back to `11`. -- Parameter `rlimit_nofile`, which in schema versions 10 and earlier used to be +- Property `rlimit_nofile`, which in schema versions 10 and earlier used to be on the top level, is now moved to the new `os` object: ```yaml @@ -291,7 +624,7 @@ In this release, the schema version has changed from 10 to 12. 'user': '' ``` - To rollback this change, move the parameter on the top level and change the + To rollback this change, move the property on the top level and change the `schema_version` back to `10`. ### Deprecated @@ -301,7 +634,7 @@ In this release, the schema version has changed from 10 to 12. ### Fixed - EDNS0 TCP keepalive option handling ([#3778]). -- Rules with the `$denyallow` modifier applying to IP addresses when they +- Rules with the `denyallow` modifier applying to IP addresses when they shouldn't ([#3175]). - The length of the EDNS0 client subnet option appearing too long for some upstream servers ([#3887]). @@ -309,8 +642,8 @@ In this release, the schema version has changed from 10 to 12. settings ([#3558]). - Incomplete propagation of the client's IP anonymization setting to the statistics ([#3890]). -- Incorrect `$dnsrewrite` results for entries from the operating system's hosts - file ([#3815]). +- Incorrect results with the `dnsrewrite` modifier for entries from the + operating system's hosts file ([#3815]). - Matching against rules with `|` at the end of the domain name ([#3371]). - Incorrect assignment of explicitly configured DHCP options ([#3744]). - Occasional panic during shutdown ([#3655]). @@ -337,8 +670,8 @@ In this release, the schema version has changed from 10 to 12. - Letter case mismatches in `CNAME` filtering ([#3335]). - Occasional breakages on network errors with DNS-over-HTTP upstreams ([#3217]). - Errors when setting static IP on Linux ([#3257]). -- Treatment of domain names and FQDNs in custom rules with `$dnsrewrite` that - use the `PTR` type ([#3256]). +- Treatment of domain names and FQDNs in custom rules with the `dnsrewrite` + modifier that use the `PTR` type ([#3256]). - Redundant hostname generating while loading static leases with empty hostname ([#3166]). - Domain name case in responses ([#3194]). @@ -421,6 +754,7 @@ In this release, the schema version has changed from 10 to 12. [#3933]: https://github.com/AdguardTeam/AdGuardHome/pull/3933 [ms-v0.107.0]: https://github.com/AdguardTeam/AdGuardHome/milestone/23?closed=1 +[rfc-9000]: https://datatracker.ietf.org/doc/html/rfc9000 @@ -501,7 +835,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0]. - The ability to block user for login after configurable number of unsuccessful attempts for configurable time ([#2826]). -- `$denyallow` modifier for filters ([#2923]). +- `denyallow` modifier for filters ([#2923]). - Hostname uniqueness validation in the DHCP server ([#2952]). - Hostname generating for DHCP clients which don't provide their own ([#2723]). - New flag `--no-etc-hosts` to disable client domain name lookups in the @@ -516,7 +850,8 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0]. network ([#2393], [#2961]). - The ability to serve DNS queries on multiple hosts and interfaces ([#1401]). - `ips` and `text` DHCP server options ([#2385]). -- `SRV` records support in `$dnsrewrite` filters ([#2533]). +- `SRV` records support in filtering rules with the `dnsrewrite` modifier + ([#2533]). ### Changed @@ -530,7 +865,8 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0]. ([#2704]). - Stricter validation of the IP addresses of static leases in the DHCP server with regards to the netmask ([#2838]). -- Stricter validation of `$dnsrewrite` filter modifier parameters ([#2498]). +- Stricter validation of `dnsrewrite` filtering rule modifier parameters + ([#2498]). - New, more correct versioning scheme ([#2412]). ### Deprecated @@ -539,7 +875,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0]. ### Fixed -- Multiple answers for `$dnsrewrite` rule matching requests with repeating +- Multiple answers for a `dnsrewrite` rule matching requests with repeating patterns in it ([#2981]). - Root server resolving when custom upstreams for hosts are specified ([#2994]). - Inconsistent resolving of DHCP clients when the DHCP server is disabled @@ -591,6 +927,10 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0]. ## [v0.105.2] - 2021-03-10 +### Security + +- Session token doesn't contain user's information anymore ([#2470]). + See also the [v0.105.2 GitHub milestone][ms-v0.105.2]. ### Fixed @@ -604,10 +944,6 @@ See also the [v0.105.2 GitHub milestone][ms-v0.105.2]. - Incomplete DNS upstreams validation ([#2674]). - Wrong parsing of DHCP options of the `ip` type ([#2688]). -### Security - -- Session token doesn't contain user's information anymore ([#2470]). - [#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470 [#2582]: https://github.com/AdguardTeam/AdGuardHome/issues/2582 [#2600]: https://github.com/AdguardTeam/AdGuardHome/issues/2600 @@ -643,8 +979,8 @@ See also the [v0.105.1 GitHub milestone][ms-v0.105.1]. - Occasional crashes during startup. - The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response is now correctly named again ([#2678]). -- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to - `false` on update anymore ([#2653]). +- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` properties aren't reset + to `false` on update anymore ([#2653]). - The `Vary` header is now added along with `Access-Control-Allow-Origin` to prevent cache-related and other issues in browsers ([#2658]). - The request body size limit is now set for HTTPS requests as well. @@ -678,7 +1014,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0]. - `ipset` subdomain matching, just like `dnsmasq` does ([#2179]). - ClientID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS ([#1383]). -- `$dnsrewrite` modifier for filters ([#2102]). +- The new `dnsrewrite` modifier for filters ([#2102]). - The host checking API and the query logs API can now return multiple matched rules ([#2102]). - Detecting of network interface configured to have static IP address via @@ -686,7 +1022,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0]. - DNSCrypt protocol support ([#1361]). - A 5 second wait period until a DHCP server's network interface gets an IP address ([#2304]). -- `$dnstype` modifier for filters ([#2337]). +- `dnstype` modifier for filters ([#2337]). - HTTP API request body size limit ([#2305]). ### Changed @@ -819,13 +1155,20 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2]. - -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...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 [v0.107.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.2...v0.107.3 [v0.107.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.1...v0.107.2 [v0.107.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.0...v0.107.1 diff --git a/Makefile b/Makefile index 51269440..b4823bb7 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ DIST_DIR = dist # See https://unix.stackexchange.com/q/646255/105635. GO.MACRO = $${GO:-go} GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct +GOSUMDB = sum.golang.google.cn GPG_KEY = devteam@adguard.com GPG_KEY_PASSPHRASE = not-a-real-password NPM = npm @@ -33,12 +34,14 @@ YARN_INSTALL_FLAGS = $(YARN_FLAGS) --network-timeout 120000 --silent\ --ignore-engines --ignore-optional --ignore-platform\ --ignore-scripts +V1API = 0 + # Macros for the build-release target. If FRONTEND_PREBUILT is 0, the # default, the macro $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) expands # 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 @@ -56,9 +59,11 @@ ENV = env\ DIST_DIR='$(DIST_DIR)'\ GO="$(GO.MACRO)"\ GOPROXY='$(GOPROXY)'\ + GOSUMDB='$(GOSUMDB)'\ PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\ RACE='$(RACE)'\ SIGN='$(SIGN)'\ + V1API='$(V1API)'\ VERBOSE='$(VERBOSE)'\ VERSION='$(VERSION)'\ 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 796d874d..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.1' + '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 @@ -183,8 +220,27 @@ cd ./dist/ + channel="${bamboo.channel}" + readonly channel + case "$channel" + in + ('release') + snapchannel='candidate' + ;; + ('beta') + snapchannel='beta' + ;; + ('edge') + snapchannel='edge' + ;; + (*) + echo "invalid channel '$channel'" + exit 1 + ;; + esac + env\ - SNAPCRAFT_CHANNEL=edge\ + SNAPCRAFT_CHANNEL="$snapchannel"\ SNAPCRAFT_EMAIL="${bamboo.snapcraftEmail}"\ SNAPCRAFT_MACAROON="${bamboo.snapcraftMacaroonPassword}"\ SNAPCRAFT_UBUNTU_DISCHARGE="${bamboo.snapcraftUbuntuDischargePassword}"\ @@ -266,7 +322,7 @@ # need to build a few of these. 'variables': 'channel': 'beta' - 'dockerGo': 'adguard/golang-ubuntu:4.1' + '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]+': @@ -281,4 +337,4 @@ # are the ones that actually get released. 'variables': 'channel': 'release' - 'dockerGo': 'adguard/golang-ubuntu:4.1' + 'dockerGo': 'adguard/golang-ubuntu:5.0' diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml index e41f172f..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.1' + '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 10079eb1..1f21106e 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -1,7 +1,7 @@ { "client_settings": "Налады кліентаў", - "example_upstream_reserved": "Вы можаце паказаць DNS-сервер <0>для канкрэтнага дамена(аў)", - "example_upstream_comment": "Вы можаце паказаць каментар", + "example_upstream_reserved": "upstream <0>для канкрэтных даменаў;", + "example_upstream_comment": "каментар.", "upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту", "parallel_requests": "Паралельныя запыты", "load_balancing": "Размеркаванне нагрузкі", @@ -36,17 +36,18 @@ "dhcp_ipv4_settings": "Налады DHCP IPv4", "dhcp_ipv6_settings": "Налады DHCP IPv6", "form_error_required": "Абавязковае поле", - "form_error_ip4_format": "Няслушны фармат IPv4", + "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": "Няслушны фармат ID кліента", + "form_error_ip6_format": "Няслушны IPv6-адрас", + "form_error_ip_format": "Няслушны IP-адрас", + "form_error_mac_format": "Некарэктны MAC-адрас", + "form_error_client_id_format": "ClientID павінен утрымліваць толькі лічбы, малыя літары і злучкі", "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": "Павінна быць больш за пачатак дыяпазону", @@ -67,11 +68,11 @@ "dhcp_table_hostname": "Імя хаста", "dhcp_table_expires": "Мінае", "dhcp_warning": "Калі вы ўсё адно хочаце ўключыць DHCP-сервер, пераканайцеся, што ў сеціве больш няма актыўных DHCP-сервераў. Інакш гэта можа зламаць доступ у сеціва для падлучаных прылад!", - "dhcp_error": "Мы не змаглі вызначыць наяўнасць іншых DHCP-сервераў у сеціве.", + "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 ", @@ -165,10 +166,10 @@ "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": "Бяспечны пошук укл.", + "disabled_parental_toast": "Адключаны бацькоўскі кантроль", + "enabled_parental_toast": "Уключаны бацькоўскі кантроль", + "disabled_safe_search_toast": "Адключаны бяспечны пошук", + "enabled_save_search_toast": "Уключаны бяспечны пошук", "enabled_table_header": "УКЛ.", "name_table_header": "Імя", "list_url_table_header": "URL-адрас спіса", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Уводзьце па адным правіле на радок. Вы можаце выкарыстоўваць правілы блакавання ці сінтаксіс файлаў hosts.", "system_host_files": "Сістэмныя hosts-файлы", "examples_title": "Прыклады", - "example_meaning_filter_block": "заблакаваць доступ да дамена example.org і ўсім яго паддаменам", - "example_meaning_filter_whitelist": "адблакаваць доступ да дамена example.org і ўсім яго паддаменам", - "example_meaning_host_block": "Зараз AdGuard Home верне 127.0.0.1 для дамена example.org (але не для яго паддаменаў).", - "example_comment": "! Так можна дадаваць апісанне", - "example_comment_meaning": "каментар", - "example_comment_hash": "# І вось так таксама", - "example_regex_meaning": "блакуе доступ да даменаў, што адпавядаюць <0>зададзенаму рэгулярнаму выразу", - "example_upstream_regular": "звычайны DNS (наўзверх UDP)", - "example_upstream_dot": "зашыфраваны DNS-па-над-TLS", - "example_upstream_doh": "зашыфраваны DNS-over-HTTPS", - "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC", - "example_upstream_sdns": "вы можаце выкарыстоўваць DNS Stamps для DNSCrypt ці DNS-over-HTTPS рэзалвераў", - "example_upstream_tcp": "звычайны DNS (наўзверх TCP)", + "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": "звычайны DNS (наўзверх UDP);", + "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_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": "Забараніць доступ гэтаму кліенту", @@ -259,7 +263,7 @@ "query_log_strict_search": "Ужывайце падвойныя двукоссі для строгага пошуку", "query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры скарачэнні інтэрвалу дадзеныя могуць быць згублены", "anonymize_client_ip": "Ананімізацыя IP-адрасы кліента", - "anonymize_client_ip_desc": "Не захоўвайце поўны IP-адрас кліента ў часопісах і статыстыцы", + "anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы", "dns_config": "Налады DNS-сервера", "dns_cache_config": "Налада кэша DNS", "dns_cache_config_desc": "Тут можна наладзіць кэш DNS", @@ -275,9 +279,9 @@ "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": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. Тут вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.", + "client_id": "ClientID", + "client_id_placeholder": "Увядзіце ClientID", + "client_id_desc": "Кліенты могуць ідэнтыфікавацца па ClientID. Тут вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.", "download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS", "download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS", "download_mobileconfig": "Загрузіць файл канфігурацыі", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Увядзіце rate limit", "rate_limit": "Ограничение скорости", "edns_enable": "Уключыць адпраўленне EDNS Client Subnet", - "edns_cs_desc": "Калі ўключыць гэту опцыю, AdGuard Home будзе адпраўляць падсеціва кліентаў на DNS-сервера.", + "edns_cs_desc": "Дадайце опцыю кліенцкай падсеткі EDNS (ECS) да запытаў вышэй па плыні і запісвайце значэнні, адпраўленыя кліентамі, у журнал запытаў.", "rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)", "blocking_ipv4_desc": "IP-адрас, што вяртаецца пры блакаванню A-запыту", "blocking_ipv6_desc": "IP-адрас, што вяртаецца пры блакаванню AAAA-запыту", @@ -309,7 +313,7 @@ "install_settings_listen": "Інтэрфейс сеціва", "install_settings_port": "Порт", "install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:", - "form_error_port": "Увядзіце карэктны порт", + "form_error_port": "Увядзіце карэктны нумар порта", "install_settings_dns": "DNS-сервер", "install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:", "install_settings_all_interfaces": "Усе інтэрфейсы", @@ -329,17 +333,17 @@ "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_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_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў «Сістэмныя налады».", - "install_devices_macos_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_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": "У меню кіравання націсніце абразок «Налады».", @@ -356,11 +360,11 @@ "open_dashboard": "Адкрыць Панэль кіравання", "install_saved": "Паспяхова захавана", "encryption_title": "Шыфраванне", - "encryption_desc": "Падтрымка шыфравання (HTTPS/TLS) для DNS і ўэб-інтэрфейсу адміністравання", + "encryption_desc": "Падтрымка шыфравання (HTTPS/QUIC/TLS) для DNS і ўэб-інтэрфейсу адміністравання", "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", @@ -368,7 +372,7 @@ "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_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце.", "encryption_certificates": "Сертыфікаты", "encryption_certificates_desc": "Для выкарыстання шыфравання вам трэба падаць валідны ланцужок SSL-сертыфікатаў для вашага дамена. Вы можаце атрымаць дармовы сертыфікат на <0>{{link}} ці вы можаце купіць яго ў аднаго з давераных Цэнтраў Сертыфікацыі.", "encryption_certificates_input": "Скапіюйце сюды сертыфікаты ў PEM-кадоўцы.", @@ -388,8 +392,8 @@ "encryption_reset": "Вы ўпэўнены, што хочаце скінуць налады шыфравання?", "topline_expiring_certificate": "Ваш SSL-сертыфікат хутка мінае. Абновіце <0>Налады шыфравання.", "topline_expired_certificate": "Ваш SSL-сертыфікат мінуў. Абновіце <0>Налады шыфравання.", - "form_error_port_range": "Увядзіце значэнне порта з інтэрвалу 80-65535", - "form_error_port_unsafe": "Гэта небяспечны порт", + "form_error_port_range": "Увядзіце нумар порта з інтэрвалу 80-65535", + "form_error_port_unsafe": "Небяспечны порт", "form_error_equal": "Не павінны быць роўныя", "form_error_password": "Паролі не супадаюць", "reset_settings": "Скінуць налады", @@ -397,15 +401,16 @@ "setup_guide": "Інструкцыя па наладзе", "dns_addresses": "Адрасы DNS", "dns_start": "DNS-сервер запускаецца", - "dns_status_error": "Памылка пры атрыманні стану DNS-сервера", + "dns_status_error": "Памылка праверкі стану DNS-сервера", "down": "Уніз", "fix": "Выправіць", "dns_providers": "<0>Спіс вядомых DNS-правайдараў на выбар.", "update_now": "Абнавіць цяпер", "update_failed": "Памылка аўто-абнаўлення. Калі ласка, кіруйцеся інструкцыі для абнаўлення ручна.", + "manual_update": "Калі ласка, кіруйцеся інструкцыі для абнаўлення ручна.", "processing_update": "Калі ласка, пачакайце, AdGuard Home абнаўляецца", - "clients_title": "Кліенты", - "clients_desc": "Наладзьце прылады, што выкарыстоўваюць AdGuard Home", + "clients_title": "Пастаянныя кліенты", + "clients_desc": "Наладзьце пастаянныя запісы кліентаў для прылад, падлучаных да AdGuard Home", "settings_global": "Глабальныя", "settings_custom": "Свае", "table_client": "Кліент", @@ -416,7 +421,7 @@ "client_edit": "Рэдагаваць кліента", "client_identifier": "Ідэнтыфікатар", "ip_address": "IP-адрас", - "client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам", + "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", @@ -425,24 +430,24 @@ "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, але не захоўваюцца ў наладах", + "auto_clients_desc": "Прылады, якіх няма ў спісе пастаянных кліентаў, якія ўсё яшчэ могуць выкарыстоўваць AdGuard Home", "access_title": "Налады доступу", - "access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home.", + "access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home", "access_allowed_title": "Дазволеныя кліенты", - "access_allowed_desc": "Спіс CIDR- ці IP-адрасоў. Калі ён наладжаны, AdGuard Home будзе прымаць запыты толькі з гэтых IP-адрасоў.", + "access_allowed_desc": "Спіс CIDR, IP-адрасоў або ClientID. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.", "access_disallowed_title": "Забароненыя кліенты", - "access_disallowed_desc": "Спіс CIDR- ці IP-адрасоў. Калі ён наладжаны, AdGuard Home будзе ігнараваць запыты з гэтых IP-адрасоў.", + "access_disallowed_desc": "Спіс CIDR, IP-адрасоў або ClientID. Калі ў гэтым спісе ёсць запісы, AdGuard Home выдаліць запыты ад гэтых кліентаў. Гэта поле ігнаруецца, калі ёсць запісы ў Дазволеныя кліенты.", "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", @@ -463,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", @@ -499,6 +504,7 @@ "interval_days": "{{count}} дзень", "interval_days_plural": "{{count}} дзён", "domain": "Дамен", + "ecs": "ECS", "punycode": "Punycode", "answer": "Адказ", "filter_added_successfully": "Спіс паспяхова дададзены", @@ -531,7 +537,7 @@ "netname": "Назва сеціва", "network": "Сеціва", "descr": "Апісанне", - "whois": "Хто", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Даведайцеся больш пра стварэнне ўласных спісаў блакавання хастоў.", "blocked_by_response": "Заблакавана па CNAME ці IP у адказе", "blocked_by_cname_or_ip": "Заблакавана з дапамогай CNAME ці IP", @@ -547,11 +553,11 @@ "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": "Тэгі", - "tags_desc": "Вы можаце выбраць тэгі, якія адпавядаюць кліенту. Тэгі могуць быць улучаны ў правілы фільтрацыі і дазволяць вам ужываць іх больш дакладна. <0>Даведацца больш.", + "tags_desc": "Вы можаце выбраць тэгі, якія адпавядаюць кліенту. Уключыце тэгі ў правілы фільтрацыі, каб прымяняць іх больш дакладна. <0>Даведацца больш.", "form_select_tags": "Выбраць тэгі кліента", "check_title": "Праверыць фільтрацыю", "check_desc": "Праверыць фільтрацыю імя хаста", @@ -567,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-адрас", @@ -597,14 +603,14 @@ "blocklist": "Чорны спіс", "milliseconds_abbreviation": "мс", "cache_size": "Памер кэша", - "cache_size_desc": "Памер кэша DNS (у байтах)", + "cache_size_desc": "Памер кэша DNS (у байтах).", "cache_ttl_min_override": "Перавызначыць мінімальны TTL", "cache_ttl_max_override": "Перавызначыць максімальны TTL", "enter_cache_size": "Увядзіце памер кэша (байты)", "enter_cache_ttl_min_override": "Увядзіце мінімальны TTL (секунды)", "enter_cache_ttl_max_override": "Увядзіце максімальны TTL (секунды)", - "cache_ttl_min_override_desc": "Перавызначыць TTL-значэнне (мінімальнае), атрыманае з upstream-сервера", - "cache_ttl_max_override_desc": "Усталюйце максімальнае TTL-значэнне (секунды) для запісаў у кэшы DNS", + "cache_ttl_min_override_desc": "Пашырыць кароткія значэнні часу жыцця (секунды), атрыманыя ад сервера вышэй па плыні пры кэшаванні адказаў DNS.", + "cache_ttl_max_override_desc": "Усталюйце максімальнае TTL-значэнне (секунды) для запісаў у кэшы DNS.", "ttl_cache_validation": "Мінімальнае значэнне TTL кэша павінна быць менш ці роўна максімальнаму значэнню", "cache_optimistic": "Аптымістычнае кэшаванне", "cache_optimistic_desc": "Прымусьце AdGuard Home адказваць з кэша, нават калі тэрмін дзеяння запісаў скончыўся, а таксама паспрабуйце абнавіць іх.", @@ -619,13 +625,13 @@ "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}}» АДКЛЮЧЫЦЬ рэжым белага спіса.", - "experimental": "Эксперыментальны", "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/__locales/bg.json b/client/src/__locales/bg.json index 8f8991f5..a64b7620 100644 --- a/client/src/__locales/bg.json +++ b/client/src/__locales/bg.json @@ -1,16 +1,22 @@ { + "client_settings": "Kлиентски настройки", + "example_upstream_comment": "Можете да поставите коментар", + "load_balancing": "Балансиране на натоварването", "check_dhcp_servers": "Проверка за активен DHCP сървър", + "save_config": "Запиши настройките", "enabled_dhcp": "DHCP е разрешен", "disabled_dhcp": "DHCP е забранен", "dhcp_title": "DHCP сървър (тестови!)", "dhcp_description": "Ако рутера ви не раздава DHCP адреси, може да използвате вградения в AdGuard DHCP сървър.", "dhcp_enable": "Рзреши DHCP сървъра", "dhcp_disable": "Забрани DHCP сървъра", + "dhcp_not_found": "Вашата мрежа няма активен DHCP сървър. Безопасно е ползването на вградения DHCP сървър.", "dhcp_found": "Вашата мрежа вече има активен DHCP сървър. Не е безопасно ползването на втори!", "dhcp_leases": "DHCP раздадени адреси", "dhcp_leases_not_found": "Няма намерени активни DHCP адреси", + "dhcp_config_saved": "Запиши конфигурацията на DHCP сървъра", "form_error_required": "Задължително поле", - "form_error_ip_format": "Невалиден IP адрес", + "form_error_ip_format": "Невалиден IPv4 адрес", "form_error_positive": "Проверете дали е положително число", "dhcp_form_gateway_input": "IP шлюз", "dhcp_form_subnet_input": "Мрежова маска", @@ -24,15 +30,21 @@ "dhcp_ip_addresses": "IP адреси", "dhcp_table_hostname": "Име на устройство", "dhcp_table_expires": "История", + "dhcp_warning": "Ако искате да използвате вградения DHCP сървър, трябва да няма друг активен DHCP в мрежата Ви!", + "country": "Държава", "city": "Град", + "delete_confirm": "Наистина ли искате да изтриете \"{{key}}\"?", + "error_details": "Подробности за грешка", "response_details": "Подробности за отговора", "request_details": "Поискайте подробности", + "details": "Детайли", "back": "Назад", "dashboard": "Табло", "settings": "Настройки", "filters": "Филтри", "filter": "Филтър", "query_log": "История на заявките", + "compact": "Compact", "faq": "ЧЗВ", "version": "версия", "address": "Адрес", @@ -60,7 +72,12 @@ "top_clients": "Най-активни IP адреси", "no_clients_found": "Нямa намерени адреси", "general_statistics": "Обща статисика", + "number_of_dns_query_24_hours": "Сума на DNS заявки за последните 24 часа", + "number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни", + "number_of_dns_query_blocked_24_hours_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": "Блокирани домейни - общи и местни филтри", @@ -70,6 +87,7 @@ "use_adguard_parental": "Включи AdGuard Родителски Надзор", "use_adguard_parental_hint": "Модул XXX в AdGuard Home ще провери дали страницата има материали за възвъстни. Използва се същия API за анонимност като при модула за Сигурност.", "enforce_safe_search": "Включи Безопасно Търсене", + "enforce_save_search_hint": "AdGuard Home прилага Безопасно Търсене в следните търсачки и сайтове: Google, Youtube, Bing, и Yandex.", "no_servers_specified": "Няма избрани услуги", "general_settings": "Общи настройки", "custom_filtering_rules": "Местни правила за филтриране", @@ -106,10 +124,13 @@ "example_comment_hash": "# Това е също коментар", "example_regex_meaning": "блокирай достъп до домейни който съвпадат със следното", "example_upstream_regular": "класически DNS (UDP протокол)", + "example_upstream_udp": "обикновен DNS (върху UDP, име на хост);", "example_upstream_dot": "криптиран DNS-върху-TLS", "example_upstream_doh": "криптиран DNS-върху-HTTPS", "example_upstream_sdns": "може да ползвате DNS Подписване за DNSCrypt или DNS-върху-HTTPS сървъри", "example_upstream_tcp": "класически DNS (TCP протокол)", + "example_upstream_tcp_hostname": "обикновен DNS (върху TCP, име на хост);", + "updated_upstream_dns_toast": "Глобалните DNS сървъри са обновени", "dns_test_ok_toast": "Въведените DNS сървъри работят коректно", "dns_test_not_ok_toast": "Сървър \"{{key}}\": не работи, моля проверете дали е въведен коректно", "unblock": "Отблокирай", @@ -135,11 +156,13 @@ "rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}", "default": "По подразбиране", "custom_ip": "Персонализиран IP", + "dns_over_https": "DNS-пред-HTTPS", "dns_over_quic": "DNS-over-QUIC", "plain_dns": "Обикновен DNS", "source_label": "Източник", "found_in_known_domain_db": "Намерен в списъците с домейни.", "category_label": "Категория", + "rule_label": "Правило", "unknown_filter": "Непознат филтър {{filterId}}", "install_welcome_title": "Добре дошли в AdGuard Home!", "install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.", @@ -147,6 +170,7 @@ "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": "Всички интерфейси", @@ -165,6 +189,7 @@ "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 настройки. В под раздел DHCP рзгледайте и намерете къде е полето за DNS настройка в което може да въведете персонализирани настройки за DNS сървъри.", "install_devices_router_list_3": "Въведете адресът на AdGuard Home сървъра.", "install_devices_windows_list_1": "Отворете Контролния Панел през Старт меню или чрез функция търсене на Windows.", @@ -192,8 +217,10 @@ "install_saved": "Успешно записано", "encryption_title": "Криптиране", "encryption_desc": "Подържа се сигурна връзка (HTTPS/TLS) включително за DNS и страницата за администрация", + "encryption_config_saved": "Конфигурацията е успешно записана", "encryption_server": "Име на сървъра", "encryption_server_enter": "Въведете име на домейна", + "encryption_server_desc": "За да използвате HTTPS, трябва името на сървъра да съвпада с това на SSL сертификата.", "encryption_redirect": "Автоматично пренасочване към HTTPS", "encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.", "encryption_https": "HTTPS порт", @@ -219,7 +246,9 @@ "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>Цъкни тук за повече информация.", @@ -230,7 +259,11 @@ "name": "Име", "clients_not_found": "Нямa намерени адреси", "check_updates_now": "Провери за актуализации", + "interval_6_hour": "6 часа", + "interval_24_hour": "24 часа", "domain": "Домейн", + "ecs": "ECS", + "statistics_clear": "Нулирай статистиката", "disabled": "Деактивиран", "username_label": "Потребител", "username_placeholder": "Въведете потребител", @@ -238,10 +271,14 @@ "password_placeholder": "Въведете парола", "network": "Мрежа", "descr": "Описание", + "try_again": "Опитай пак", + "disable_ipv6": "Изключете IPv6 протокола", "show_blocked_responses": "Блокирано", "show_whitelisted_responses": "В белия списък", "show_processed_responses": "Обработен", "allowed": "В белия списък", + "safe_search": "Безопасно търсене", + "blocklist": "Черен списък", "filter_category_general": "General", "filter_category_security": "Сигурност", "port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция как да решите това.", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index 95935953..a01e1c92 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -1,7 +1,7 @@ { "client_settings": "Nastavení klienta", - "example_upstream_reserved": "Můžete zadat odchozí DNS připojení <0>pro konkrétní doménu(y)", - "example_upstream_comment": "Můžete zadat komentář", + "example_upstream_reserved": "odchozí DNS připojení <0>pro konkrétní doménu(y);", + "example_upstream_comment": "komentář.", "upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.", "parallel_requests": "Paralelní požadavky", "load_balancing": "Optimalizace vytížení", @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS servery", "bootstrap_dns_desc": "Servery Bootstrap DNS se používají k řešení IP adres DoH/DoT, které zadáváte jako upstreamy.", "local_ptr_title": "Soukromé reverzní DNS servery", - "local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k rozlišení názvů hostitelů klientů se soukromými adresami IP, například \"192.168.12.34\" pomocí rDNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.", + "local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k řešení požadavků PTR na adresy v soukromých rozmezích IP, například \"192.168.12.34\", pomocí reverzního DNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.", "local_ptr_default_resolver": "Ve výchozím nastavení používá AdGuard Home následující reverzní DNS řešitele: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home nemohl určit vhodné soukromé reverzní DNS řešitele pro tento systém.", "local_ptr_placeholder": "Zadejte jednu adresu serveru na řádek", @@ -41,12 +41,13 @@ "form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu", "form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány", "form_error_ip6_format": "Neplatná adresa IPv6", - "form_error_ip_format": "Neplatná adresa IP", + "form_error_ip_format": "Neplatná IP adresa", "form_error_mac_format": "Neplatná adresa MAC", "form_error_client_id_format": "ID klienta musí obsahovat pouze čísla, malá písmena a spojovníky", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Název hostitele", "dhcp_table_expires": "Vyprší", "dhcp_warning": "Pokud přesto chcete server DHCP povolit, ujistěte se, že ve Vaší síti není žádný jiný aktivní server DHCP, protože by to mohlo narušit připojení k Internetu pro zařízení v síti!", - "dhcp_error": "AdGuard Home nemohl určit, zda je v síti jiný aktivní server DHCP.", + "dhcp_error": "AdGuard Home nemohl určit, zda je v síti jiný aktivní server DHCP", "dhcp_static_ip_error": "Pro použití serveru DHCP musí být nastavena statická IP adresa. AdGuard Home nemohl zjistit, zda je toto síťové rozhraní nakonfigurováno pomocí statické adresy IP. Nastavte prosím statickou IP adresu ručně.", "dhcp_dynamic_ip_found": "Váš systém používá konfiguraci dynamické IP adresy pro rozhraní <0>{{interfaceName}}. Pro použití serveru DHCP musí být nastavena statická IP adresa. Vaše aktuální IP adresa je <0>{{ipAddress}}. AdGuard Home automaticky nastaví tuto IP adresu jako statickou, pokud stisknete tlačítko \"Zapnout DHCP server\".", "dhcp_lease_added": "Statický pronájem \"{{key}}\" byl úspěšně přidán", @@ -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", @@ -204,21 +205,24 @@ "examples_title": "Příklady", "example_meaning_filter_block": "zablokovat přístup k doméně example.org a všem jejím subdoménám", "example_meaning_filter_whitelist": "odblokovat přístup k doméně example.org a všem jejím subdoménám", - "example_meaning_host_block": "AdGuard Home nyní vrátí adresu 127.0.0.1 pro doménu example.org (ale ne pro její subdomény).", - "example_comment": "! Sem se přidává komentář", - "example_comment_meaning": "jen komentář", - "example_comment_hash": "# Také komentář", - "example_regex_meaning": "blokuje přístup doménám, které vyhovují regulárnímu výrazu", - "example_upstream_regular": "obyčejný DNS (přes UDP)", - "example_upstream_dot": "šifrovaný <0>DNS skrze TLS", - "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS", - "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC", - "example_upstream_sdns": "můžete použít <0>DNS razítka pro <1>DNSCrypt nebo <2>DNS skrze HTTPS řešitele", - "example_upstream_tcp": "obyčejný DNS (přes TCP)", + "example_meaning_host_block": "odezva s adresou 127.0.0.1 pro doménu example.org (ale ne pro její subdomény);", + "example_comment": "! Sem se přidává komentář.", + "example_comment_meaning": "jen komentář;", + "example_comment_hash": "# Také komentář.", + "example_regex_meaning": "blokuje přístup doménám, které vyhovují regulárnímu výrazu.", + "example_upstream_regular": "obvyklý DNS (přes UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Všechny seznamy jsou již aktuální", "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", @@ -277,15 +281,15 @@ "dns_over_quic": "DNS skrze QUIC", "client_id": "ID klienta", "client_id_placeholder": "Zadejte ID klienta", - "client_id_desc": "Různé klienty lze identifikovat pomocí speciálního ID klienta. Zde se můžete dozvědět více o tom, jak klienty identifikovat.", + "client_id_desc": "Klienty lze identifikovat pomocí ID klienta. Zde se můžete dozvědět více o tom, jak klienty identifikovat.", "download_mobileconfig_doh": "Stáhnout .mobileconfig pro DNS skrze HTTPS", "download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS", "download_mobileconfig": "Stáhnout konfigurační soubor", - "plain_dns": "Čisté DNS", + "plain_dns": "Běžný DNS", "form_enter_rate_limit": "Zadejte rychlostní limit", "rate_limit": "Rychlostní limit", "edns_enable": "Povolit klientskou podsíť EDNS", - "edns_cs_desc": "Odeslat podsítě klientů na servery DNS.", + "edns_cs_desc": "Přidá možnost podsítě klienta EDNS (ECS) do odchozích požadavků a zaznamá hodnoty odeslané klienty do protokolu dotazů.", "rate_limit_desc": "Počet požadavků za sekundu, které smí jeden klient provádět (0: neomezeno)", "blocking_ipv4_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu A", "blocking_ipv6_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu AAAA", @@ -334,7 +338,7 @@ "install_devices_router_list_4": "Na některých typech routerů nemůžete nastavit vlastní DNS server. V tomto případě může AdGuard Home pomoci, pokud jej nastavíte jako <0>DHCP server. V ostatních případech byste si v manuálu k Vašemu routeru měli zjistit, jak přizpůsobit vlastní DNS servery.", "install_devices_windows_list_1": "Otevřete ovládací panel prostřednictvím nabídky Start nebo vyhledání v systému Windows.", "install_devices_windows_list_2": "Přejděte na kategorii Síť a Internet a poté na Centrum sítí a sdílení.", - "install_devices_windows_list_3": "Na levé straně obrazovky najděte možnost \"Změnit nastavení adaptéru\" a klepněte na něj.", + "install_devices_windows_list_3": "Na levé straně panelu klikněte na \"Změnit nastavení adaptéru\".", "install_devices_windows_list_4": "Vyberte své aktivní spojení, klikněte na něj pravým tlačítkem myši a zvolte Vlastnosti.", "install_devices_windows_list_5": "V seznamu najděte \"Internet Protocol Version 4 (TCP/IP)\", (nebo IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), vyberte jej a znovu klikněte na Vlastnosti.", "install_devices_windows_list_6": "Zvolte \"Použít následující adresy serveru DNS\" a zadejte adresy serveru AdGuard Home.", @@ -360,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": "DNS skrze QUIC port", - "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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování.", "topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování.", "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535", - "form_error_port_unsafe": "Toto není bezpečný port", + "form_error_port_unsafe": "Nezabezpečený port", "form_error_equal": "Nesmí se shodovat", "form_error_password": "Heslo se neshoduje", "reset_settings": "Resetovat nastavení", @@ -405,8 +409,8 @@ "update_failed": "Automatická aktualizace selhala. Prosím následujte tyto kroky a aktualizujte ručně.", "manual_update": "Prosím následujte tyto kroky a aktualizujte ručně.", "processing_update": "Čekejte prosím, AdGuard Home se aktualizuje", - "clients_title": "Klienti", - "clients_desc": "Konfigurace zařízení připojených k AdGuard Home", + "clients_title": "Stálí klienti", + "clients_desc": "Konfigurace stálých klientských záznamů pro zařízení připojená k AdGuard Home", "settings_global": "Globální", "settings_custom": "Vlastní", "table_client": "Klient", @@ -417,7 +421,7 @@ "client_edit": "Upravit klienta", "client_identifier": "Identifikátor", "ip_address": "IP adresa", - "client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy, CIDR, MAC adresy nebo speciálního ID klienta (může být použito pro DoT/DoH/DoQ). <0>Zde se můžete dozvědět více o tom, jak klienty identifikovat.", + "client_identifier_desc": "Klienti můžou být identifikováni podle jejich IP adresy, CIDR, MAC adresy nebo ID klienta (může být použito pro DoT/DoH/DoQ). <0>Zde se můžete dozvědět více o tom, jak klienty identifikovat.", "form_enter_ip": "Zadejte IP", "form_enter_subnet_ip": "Zadejte adresu IP adresu do podsítě \"{{cidr}}\"", "form_enter_mac": "Zadejte MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Nenalezeni žádní klienti", "client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?", "list_confirm_delete": "Opravdu chcete smazat tento seznam?", - "auto_clients_title": "Klienti (doba spuštění)", - "auto_clients_desc": "Data o klientech, kteří používají AdGuard Home, ale nejsou uloženi v konfiguraci", + "auto_clients_title": "Spuštění klienti", + "auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home", "access_title": "Nastavení přístupu", - "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home.", + "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home", "access_allowed_title": "Povolení klienti", - "access_allowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud je nakonfigurován, AdGuard Home bude přijímat požadavky pouze od těchto klientů.", + "access_allowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud tento seznam obsahuje položky, AdGuard Home bude přijímat požadavky pouze od těchto klientů.", "access_disallowed_title": "Blokovaní klienti", - "access_disallowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud je nakonfigurován, AdGuard Home bude odmítat požadavky od těchto klientů. Pokud jsou povolení klienti nakonfigurováni, je toto pole ignorováno.", + "access_disallowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud tento seznam obsahuje položky, AdGuard Home bude odmítat požadavky od těchto klientů. Pokud jsou povolení klienti nakonfigurováni, je toto pole ignorováno.", "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", @@ -500,6 +504,7 @@ "interval_days": "Dny: {{count}}", "interval_days_plural": "Dny: {{count}}", "domain": "Doména", + "ecs": "ECS", "punycode": "Punycode", "answer": "Odpověď", "filter_added_successfully": "Seznam byl úspěšně přidán", @@ -532,7 +537,7 @@ "netname": "Název sítě", "network": "Síť", "descr": "Popis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Další informace o vytváření vlastních seznamů hostitelů.", "blocked_by_response": "Zakázáno dle CNAME nebo IP v odpovědi", "blocked_by_cname_or_ip": "Zakázáno dle CNAME nebo IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Jsou prováděny následující úlohy: <0>Deaktivace systému DNSStubListener <0>Nastavení adresy serveru DNS na 127.0.0.1 <0>Nahrazení cíle symbolického odkazu z /etc/resolv.conf do /run/systemd/resolve/resolv.conf <0>Zastavení služby DNSStubListener (znovu načtení služby systemd-resolved)", "autofix_warning_result": "Výsledkem je, že všechny požadavky DNS z vašeho systému jsou ve výchozím nastavení zpracovány službou AdGuard Home.", "tags_title": "Značky", - "tags_desc": "Můžete vybrat značky, které jsou přiřazeny klientovi. Značky mohou být zahrnuty do pravidel filtrování a umožňují Vám je přesněji použít. <0>Dozvědět se více", + "tags_desc": "Můžete vybrat značky, které jsou přiřazeny klientovi. Značky mohou být zahrnuty do pravidel filtrování a umožňují Vám je přesněji použít. <0>Dozvědět se více.", "form_select_tags": "Vyberte značky klienta", "check_title": "Zkontrolovat filtrování", - "check_desc": "Zkontrolujte, zda je název hostitele filtrován", + "check_desc": "Zkontrolujte, zda je název hostitele filtrován.", "check": "Zkontrolovat", "form_enter_host": "Zadejte název hostitele", "filtered_custom_rules": "Filtrováno pomocí vlastních pravidel filtrování", @@ -598,15 +603,15 @@ "blocklist": "Zakázaný", "milliseconds_abbreviation": "ms", "cache_size": "Velikost mezipaměti", - "cache_size_desc": "Velikost mezipaměti DNS (v bajtech)", + "cache_size_desc": "Velikost mezipaměti DNS (v bajtech).", "cache_ttl_min_override": "Přepsat minimální hodnotu TTL", "cache_ttl_max_override": "Přepsat maximální hodnotu TTL", "enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)", "enter_cache_ttl_min_override": "Zadejte minimální hodnotu TTL (v sekundách)", "enter_cache_ttl_max_override": "Zadejte maximální hodnotu TTL (v sekundách)", - "cache_ttl_min_override_desc": "Prodlužte nejkratší hodnotu TTL (v sekundách) obdrženou z odchozího serveru při ukládání DNS odpovědí do mezipaměti", - "cache_ttl_max_override_desc": "Nastavte maximální hodnotu TTL (v sekundách) pro položky v mezipaměti DNS", - "ttl_cache_validation": "Minimální hodnota TTL mezipaměti musí být menší nebo rovna maximální hodnotě", + "cache_ttl_min_override_desc": "Prodlužte nejkratší hodnotu TTL (v sekundách) obdrženou z odchozího serveru při ukládání DNS odpovědí do mezipaměti.", + "cache_ttl_max_override_desc": "Nastavte maximální hodnotu TTL (v sekundách) pro položky v mezipaměti DNS.", + "ttl_cache_validation": "Minimální přepis TTL mezipaměti musí být menší nebo roven maximální hodnotě", "cache_optimistic": "Optimistické ukládání do mezipaměti", "cache_optimistic_desc": "Nechte AdGuard Home odpovědět z mezipaměti, i když už platnost položek skončila. Také se je pokuste obnovit.", "filter_category_general": "Obecné", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home zruší všechny DNS dotazy tohoto klienta.", "filter_allowlist": "VAROVÁNÍ: Tato akce také vyloučí pravidlo \"{{disallowed_rule}}\" ze seznamu povolených klientů.", "last_rule_in_allowlist": "Nelze zakázat tohoto klienta, protože vyloučení pravidla \"{{disallowed_rule}}\" ZRUŠÍ seznam \"Povolených klientů\".", - "experimental": "Experimentální", "use_saved_key": "Použít dříve uložený klíče", "parental_control": "Rodičovská ochrana", "safe_browsing": "Bezpečné prohlížení", - "served_from_cache": "{{value}} (převzato z mezipaměti)" + "served_from_cache": "{{value}} (převzato z mezipaměti)", + "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé" } diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index 04d8a393..32bec1e2 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -1,7 +1,7 @@ { "client_settings": "Klientindstillinger", - "example_upstream_reserved": "DNS-upstream kan agives <0>for et eller flere specifikke domæner", - "example_upstream_comment": "Du kan angive en kommentaren", + "example_upstream_reserved": "en upstream <0>for bestemte domæner;", + "example_upstream_comment": "en kommentaren.", "upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.", "parallel_requests": "Parallelle forespørgsler", "load_balancing": "Belastningsfordeling", @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS-servere", "bootstrap_dns_desc": "Bootstrap DNS-servere bruges til at fortolke IP-adresser for de DoH-/DoT-resolvere, du angiver som upstream.", "local_ptr_title": "Private reverse DNS-servere", - "local_ptr_desc": "De DNS-servere, som AdGuard Home bruger til lokale PTR-forespørgsler. Disse servere bruges til at opløse klientværtsnavne med private IP-adresser, f.eks. \"192.168.12.34\", vha. rDNS. Hvis ikke indstillet, bruger AdGuard Home dit operativsystems standard DNS-opløsere undtagen for sine egne adresser.", + "local_ptr_desc": "DNS-servere brugt af AdGuard Home til lokale PTR-forespørgsler. Disse servere bruges til at opløse PTR-forespørgsler fra private IP-adresseområder, f.eks. \"192.168.12.34\", vha. reverse DNS. Hvis ikke opsat, bruger AdGuard Home operativsystems standard DNS-opløsere undtagen for sine egne adresser.", "local_ptr_default_resolver": "AdGuard Home bruger som standard flg. reverse DNS-opløsere: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home kunne ikke fastslå egnede private reverse DNS-opløsere for dette system.", "local_ptr_placeholder": "Indtast en serveradresse pr. Linje", @@ -37,20 +37,21 @@ "dhcp_ipv6_settings": "DHCP IPv6-indstillinger", "form_error_required": "Obligatorisk felt", "form_error_ip4_format": "Ugyldig IPv4-adresse", - "form_error_ip4_range_start_format": "Ugyldig IPv4-adresse for områdestart", - "form_error_ip4_range_end_format": "Ugyldig IPv4-adresse for områdeafslutning", - "form_error_ip4_gateway_format": "Ugyldig IPv4-adresse for gateway", + "form_error_ip4_range_start_format": "Ugyldig IPv4-startadresse for området", + "form_error_ip4_range_end_format": "Ugyldig IPv4-slutadresse for området", + "form_error_ip4_gateway_format": "Ugyldig IPv4 gateway-adresse", "form_error_ip6_format": "Ugyldig IPv6-adresse", "form_error_ip_format": "Ugyldig IP-adresse", "form_error_mac_format": "Ugyldig MAC-adresse", - "form_error_client_id_format": "Klient-ID må kun indeholde cifre, minuskler og bindestreger", + "form_error_client_id_format": "KlientID må kun indeholde cifre, minuskler og bindestreger", "form_error_server_name": "Ugyldigt servernavn", - "form_error_subnet": "Subnet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"", + "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", - "greater_range_end_error": "Skal være større end slutningen på ​​området", + "greater_range_end_error": "Skal være større end områdeslutning", "subnet_error": "Adresser ska være i ét undernet", "gateway_or_subnet_invalid": "Undernetmaske ugyldig", "dhcp_form_gateway_input": "Gateway IP", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Værtsnavn", "dhcp_table_expires": "Udløber", "dhcp_warning": "Vil du alligevel aktivere DHCP-serveren, så sørg for at der ikke er nogen anden aktiv DHCP-server på dit netværk, da dette kan ødelægge Internetkonnektiviteten for netværksenhederne!", - "dhcp_error": "AdGuard Home kunne ikke afgøres, om der findes en anden DHCP-server på netværket.", + "dhcp_error": "AdGuard Home kunne ikke afgøres, om der findes en anden DHCP-server på netværket", "dhcp_static_ip_error": "For at kunne bruge DHCP-serveren skal der opsættes en statisk IP-adresse. Da det ikke kunne afgøres, om denne netværksinterface er opsat vha. en statisk IP-adresse, bedes du opsætte en manuelt.", "dhcp_dynamic_ip_found": "Dit system bruger en dynamisk IP-adresseopsætning til interface <0>{{interfaceName}}. For at kunne bruge DHCP-serveren skal en statisk IP-adresse indstilles. Din aktuelle IP-adresse er <0>{{ipAddress}}. AdGuard Home vil automatisk indstille denne IP-adresse som din statiske hvis du trykker på knappen \"Aktivér DHCP-server\".", "dhcp_lease_added": "Statisk lease \"{{key}}\" tilføjet", @@ -197,28 +198,31 @@ "enter_valid_blocklist": "Angiv en gyldig URL til sortlisten.", "enter_valid_allowlist": "Angiv en gyldig URL til hvidlisten.", "form_error_url_format": "Ugyldigt URL-format", - "form_error_url_or_path_format": "Ugyldig URL eller absolut listesti", + "form_error_url_or_path_format": "Ugyldig URL eller absolut sti til liste.", "custom_filter_rules": "Tilpassede filtreringsregler", "custom_filter_rules_hint": "Angiv én regel pr. linje. Du kan bruge enten adblockingregler eller værtsfilsyntaks.", "system_host_files": "System hosts-filer", "examples_title": "Eksempler", - "example_meaning_filter_block": "blokér adgang til example.org-domænet samt alle underdomæner", - "example_meaning_filter_whitelist": "afblokér adgang til example.ord-domænet samt alle underdomæner", - "example_meaning_host_block": "AdGuard Home returnerer nu adressen 127.0.0.1 for example.org-domænet (men ikke underdomænerne).", - "example_comment": "! Hér angives en evt. kommentar", - "example_comment_meaning": "bare en kommentar", - "example_comment_hash": "# Også en kommentar", + "example_meaning_filter_block": "blokér adgang til eksmpel.dk-domænet og alle underdomæner;", + "example_meaning_filter_whitelist": "afblokér adgang til eksempel.dk-domænet og alle underdomæner;", + "example_meaning_host_block": "besvar med 127.0.0.1 for eksempel.dk-domænet (men ikke underdomænerne);", + "example_comment": "! Hér angives en kommentar.", + "example_comment_meaning": "kun en kommentar;", + "example_comment_hash": "# Også en kommentar.", "example_regex_meaning": "blokér adgang til domæner matchernde det angivne regulære udtryk", "example_upstream_regular": "almindelig DNS (over UDP)", + "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", - "example_upstream_sdns": "du kan bruge <0>DNS Stamps til <1>DNSCrypt eller <2>DNS-over-HTTPS-resolvers", + "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);", "all_lists_up_to_date_toast": "Alle lister er allerede opdaterede", "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", @@ -259,10 +263,10 @@ "query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning", "query_log_retention_confirm": "Sikker på, at du vil ændre forespørgselsloggens opbevaringperiode? Mindskes intervalværdien, mistes data", "anonymize_client_ip": "Anonymisér klient-IP", - "anonymize_client_ip_desc": "Gem ikke klientens fulde IP-adresse i logfiler og statistikker", + "anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker", "dns_config": "DNS-serveropsætning", "dns_cache_config": "DNS-cacheopsætning", - "dns_cache_config_desc": "Hér kan du opsætte DNS-cache", + "dns_cache_config_desc": "Hér kan DNS-cache opsættes.", "blocking_mode": "Blokeringstilstand", "default": "Standard", "nxdomain": "NXDOMAIN", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-Quic", - "client_id": "Klient-ID", - "client_id_placeholder": "Angiv klient-ID", - "client_id_desc": "Forskellige klienter kan identificeres via et specielt klient-ID. Hér finder du ud af mere om, hvordan klienter identificeres.", + "client_id": "KlientID", + "client_id_placeholder": "Angiv en KlientID", + "client_id_desc": "Klienter kan identificeres via KlientID. Læs mere om, hvordan klienter identificeres hér.", "download_mobileconfig_doh": "Download .mobileconfig til DNS-over-HTTPS", "download_mobileconfig_dot": "Download .mobileconfig til DNS-over-TLS", "download_mobileconfig": "Download opsætningsfil", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Angiv hyppighedsgrænse", "rate_limit": "Hyppighedsgrænse", "edns_enable": "Aktivér EDNS-klientundernet", - "edns_cs_desc": "Send klienters undernet til DNS-serverne.", + "edns_cs_desc": "Tilføj indstillingen EDNS Client Subnet (ECS) til upstream-forespørgsler og log de af klienterne sendte værdier i forespørgselsloggen.", "rate_limit_desc": "Antallet af forespørgsler pr. sekund tilladt pr. klient (værdien 0 = ubegrænset)", "blocking_ipv4_desc": "Returneret IP-adresse for en blokeret A-forespørgsel", "blocking_ipv6_desc": "Returneret IP-adresse for en blokeret AAAA-forespørgsel", @@ -309,7 +313,7 @@ "install_settings_listen": "Overvågningsgrænseflade", "install_settings_port": "Port", "install_settings_interface_link": "Din AdGuard Home admin webgrænseflade vil være tilgængelig på flg. adresser:", - "form_error_port": "Angiv gyldig portnummer", + "form_error_port": "Angiv gyldigt portnummer", "install_settings_dns": "DNS-server", "install_settings_dns_desc": "Du skal opsætte dine enheder eller router til at bruge DNS-serveren på flg. adresser:", "install_settings_all_interfaces": "Alle grænseflader", @@ -334,11 +338,11 @@ "install_devices_router_list_4": "På visse routertyper kan en tilpasset DNS-server ikke opsættes. I så tilfælde kan det hjælpe, hvis du opsætter AdGuard Home som en <0>DHCP-server. Ellers bør du tjekke i routermanualen, hvordan du tilpasser DNS-servere i din givne routermodel.", "install_devices_windows_list_1": "Åbn Kontrolpanel via menuen Start eller Windows-søgning.", "install_devices_windows_list_2": "Gå til kategorien Netværk og Internet og derefter til Netværks- og delingscenter.", - "install_devices_windows_list_3": "Find punktet \"Skift adapterindstillinger\" til venstre på skærmen, klik på det.", - "install_devices_windows_list_4": "Vælg din aktive forbindelse, højreklik på den og vælg Egenskaber.", + "install_devices_windows_list_3": "Find og klik på \"Skift adapterindstillinger\" i venstre panel.", + "install_devices_windows_list_4": "Højreklik på den aktive forbindelse, og vælg Egenskaber.", "install_devices_windows_list_5": "Find \"Internet Protocol Version 4 (TCP/IPv4)\" (eller for IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\") på listen, vælg den og klik derefter på Egenskaber igen.", "install_devices_windows_list_6": "Vælg \"Brug følgende DNS-serveradresser og angiv dine AdGuard Home-serveradresser.", - "install_devices_macos_list_1": "Klik på Apple-ikonet og gå til Systemindstillinger.", + "install_devices_macos_list_1": "Klik på Apple-ikonet og gå til Systempræferencer.", "install_devices_macos_list_2": "Klik på Netværk.", "install_devices_macos_list_3": "Vælg den første forbindelse på din liste, og klik på Avanceret.", "install_devices_macos_list_4": "Vælg fanen DNS og angiv dine AdGuard Home-serveradresser.", @@ -349,18 +353,18 @@ "install_devices_android_list_5": "Skift de aktuelle DNS 1- og DNS 2-værdier til dine AdGuard Home-serveradresser.", "install_devices_ios_list_1": "Tryk på Indstillinger på Hjem-skærmen.", "install_devices_ios_list_2": "Vælg Wi-Fi i menuen til venstre (det er umuligt at opsætte DNS for mobilnetværker).", - "install_devices_ios_list_3": "Tryk på navnet på det aktuelt aktive netværk.", + "install_devices_ios_list_3": "Tryk på navnet for det aktuelt aktive netværk.", "install_devices_ios_list_4": "Angiv dine AdGuard Home-serveradresser i DNS-feltet.", "get_started": "Komme I Gang", "next": "Næste", "open_dashboard": "Åbn Dashboard", "install_saved": "Gemt", "encryption_title": "Kryptering", - "encryption_desc": "Kryptering (HTTPS/TLS) understøtter både DNS og admin webgrænseflade", + "encryption_desc": "Krypteringsunderstøttelse (HTTPS/TLS) til både DNS og admin-webgrænseflade", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -389,15 +393,15 @@ "topline_expiring_certificate": "Dit SSL-certifikat er ved at udløbe. Opdatér <0>Krypteringsindstillinger.", "topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger.", "form_error_port_range": "Angiv portnummer i intervallet 80-65535", - "form_error_port_unsafe": "Dette er en usikker port", - "form_error_equal": "Må ikke være ens", - "form_error_password": "Adgangskoden matcher ikke", + "form_error_port_unsafe": "Ikke-sikker port", + "form_error_equal": "Må ikke svare til.", + "form_error_password": "Adgangskoder matcher ikke.", "reset_settings": "Nulstil indstillinger", "update_announcement": "AdGuard Home {{version}} er nu tilgængelig! <0>Kik hér for mere info.", "setup_guide": "Installationsvejledning", "dns_addresses": "DNS-adresser", "dns_start": "DNS-server starter", - "dns_status_error": "Fejl ved tjek af DNS-serverstatus", + "dns_status_error": "Fejl under tjek af DNS-serverstatus.", "down": "Ned", "fix": "Korrigér", "dns_providers": "Her er en <0>liste over kendte DNS-udbydere at vælge imellem.", @@ -405,8 +409,8 @@ "update_failed": "Autoopdatering mislykkedes. Følg disse trin for at opdatere manuelt.", "manual_update": "Følg disse trin for at opdatere manuelt.", "processing_update": "Vent venligst, AdGuard Home bliver opdateret", - "clients_title": "Klienter", - "clients_desc": "Opsæt enheder forbundet til AdGuard Home", + "clients_title": "Blivende klienter", + "clients_desc": "Opsæt permanente klientposter for enheder tilsluttet AdGuard Home", "settings_global": "Global", "settings_custom": "Tilpasset", "table_client": "Klient", @@ -417,7 +421,7 @@ "client_edit": "Redigér Klient", "client_identifier": "Identifikator", "ip_address": "IP-adresse", - "client_identifier_desc": "Klienter kan identificeres ud fra IP-/MAC-adresser, CIDR eller et særligt klient-ID (kan bruges til DoT/DoH/DoQ). <0>Hér kan du læse mere om, hvordan klienter identificeres.", + "client_identifier_desc": "Klienter kan identificeres ud fra IP-/MAC-adresser, CIDR eller et særligt KlientID (kan bruges til DoT/DoH/DoQ). Læs mere om, hvordan klienter identificeres <0>hér.", "form_enter_ip": "Angiv IP", "form_enter_subnet_ip": "Indtast en IP-adresse i subnettet \"{{cidr}}\"", "form_enter_mac": "Angiv MAC", @@ -433,17 +437,17 @@ "client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?", "list_confirm_delete": "Sikker på, at du vil slette denne liste?", "auto_clients_title": "Klienter (runtime)", - "auto_clients_desc": "Data om de klienter, som bruger AdGuard Home, men ikke gemt i opsætningen", + "auto_clients_desc": "Enheder, som ikke er på listen over Permanente klienter, kan stadig bruge AdGuard Home", "access_title": "Adgangsindstillinger", - "access_desc": "Her kan du opsætte adgangsregler for AdGuard Home DNS-serveren.", + "access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes", "access_allowed_title": "Tilladte klienter", - "access_allowed_desc": "En liste over CIDR-, IP-adresser eller klient-ID'er. Hvis opsat, accepterer AdGuard Home kun forespørgsler fra disse klienter.", + "access_allowed_desc": "En liste over CIDR'er, IP-adresser eller KlientID'er. Har listen poster, accepterer AdGuard Home kun forespørgsler fra disse klienter.", "access_disallowed_title": "Ikke tilladte klienter", - "access_disallowed_desc": "En liste over CIDR-, IP-adresser eller klient-ID'er. Hvis opsat, dropper AdGuard Home forespørgsler fra disse klienter. Opsættes tilladte klienter, ignoreres dette felt.", + "access_disallowed_desc": "En liste over CIDR'er, IP-adresser eller KlientID'er. Har listen poster, dropper AdGuard Home forespørgsler fra disse klienter. Har Tilladte klienter poster, ignoreres dette felt.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dag", "interval_days_plural": "{{count}} dage", "domain": "Domæne", + "ecs": "ECS", "punycode": "Punycode", "answer": "Svar", "filter_added_successfully": "Listen er tilføjet", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} timer", "filters_configuration": "Filteropsætninger", "filters_enable": "Aktivér filtre", - "filters_interval": "Filtrenes opdateringsinterval", + "filters_interval": "Filteropdateringsinterval", "disabled": "Deaktiveret", "username_label": "Brugernavn", "username_placeholder": "Angiv brugernavn", @@ -532,7 +537,7 @@ "netname": "Netværksnavn", "network": "Netværk", "descr": "Beskrivelse", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Læs mere om at oprette dine egne værtslister.", "blocked_by_response": "Blokeret af CNAME eller IP i svar", "blocked_by_cname_or_ip": "Blokeret af CNAME eller IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Den vil udføre disse opgaver: <0>Deaktivere system DNSStubListener <0>Opsætte DNS-serveradressen til 127.0.0.1 <0>Erstatte symbolsk linkmål for /etc/resolv.conf med /run/systemd/resolve/resolv.conf <0>Stoppe DNSStubListener (genindlæs systemd-opløst tjeneste)", "autofix_warning_result": "Det betyder, at alle DNS-forespørgsler fra dit system som standard behandles af AdGuard Home.", "tags_title": "Tags", - "tags_desc": "Du kan vælge de tags, som svarer til klienten. Tags kan inkluderes i filtreringsreglerne, hvilket lader anvende dem mere præcist. <0>Læs mere", + "tags_desc": "Der kan vælges tags, som svarer til klienten. Medtag tags i filtreringsregler for at anvende dem mere præcist. <0>Læs mere.", "form_select_tags": "Vælg klient tags", "check_title": "Tjek filtreringen", - "check_desc": "Tjek, om værtsnavnet er filtreret", + "check_desc": "Tjek, om værtsnavnet filtreres.", "check": "Tjek", "form_enter_host": "Angiv et værtsnavn", "filtered_custom_rules": "Filtreret af tilpassede filtreringsregler", @@ -598,14 +603,14 @@ "blocklist": "Sortliste", "milliseconds_abbreviation": "ms", "cache_size": "Cache-størrelse", - "cache_size_desc": "DNS-cache størrelse (i bytes)", + "cache_size_desc": "DNS cache-størrelse (i bytes).", "cache_ttl_min_override": "Tilsidesæt minimum TTL", "cache_ttl_max_override": "Tilsidesæt maksimal TTL", "enter_cache_size": "Angiv cache-størrelse (bytes)", "enter_cache_ttl_min_override": "Angiv minimum TTL (sekunder)", "enter_cache_ttl_max_override": "Angiv maksimum TTL (sekunder)", "cache_ttl_min_override_desc": "Forlæng korte time-to-live værdier (sekunder) modtaget fra upstream-serveren, når DNS-svar cachelagres", - "cache_ttl_max_override_desc": "Indstil en maksimal time-to-live (sekunder) for registreringer i DNS-cachen", + "cache_ttl_max_override_desc": "Angiv en maksimal time-to-live (sekunder) for poster i DNS-cachen.", "ttl_cache_validation": "Minimum cache TTL-værdi skal være mindre end eller lig med den maksimale værdi", "cache_optimistic": "Optimistisk caching", "cache_optimistic_desc": "Får AdGuard Home til at svare fra cachen, selv når posterne er udløbet, og prøver også at opdatere dem.", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home vil afbryde alle DNS-forespørgsler fra denne klient.", "filter_allowlist": "ADVARSEL: Denne handling udelukker også reglen \"{{disallowed_rule}}\" fra listen over tilladte klienter.", "last_rule_in_allowlist": "Kan ikke afvise denne klient, da udelukkelse af reglen \"{{disallowed_rule}}\" DEAKTIVERER listen \"Tilladte klienter\".", - "experimental": "Eksperimentel", "use_saved_key": "Brug den tidligere gemte nøgle", "parental_control": "Forældrekontrol", "safe_browsing": "Sikker Browsing", - "served_from_cache": "{{value}} (leveret fra cache)" + "served_from_cache": "{{value}} (leveret fra cache)", + "form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn." } diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 4fb4cd02..a1e49718 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -1,7 +1,7 @@ { "client_settings": "Client-Einstellungen", - "example_upstream_reserved": "Sie können DNS-Upstream <0>für bestimmte Domain(s) angeben", - "example_upstream_comment": "Sie können den Kommentar angeben", + "example_upstream_reserved": "ein Upstream <0>für bestimmte Domains;", + "example_upstream_comment": "ein Kommentar.", "upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.", "parallel_requests": "Paralleles Abfragen", "load_balancing": "Lastverteilung", @@ -39,14 +39,15 @@ "form_error_ip4_format": "Ungültige IPv4-Adresse", "form_error_ip4_range_start_format": "Ungültiger Bereichsbeginn der IPv4-Adresse", "form_error_ip4_range_end_format": "Ungültiges Bereichsende der IPv4-Adresse", - "form_error_ip4_gateway_format": "Ungültiges Gateway-IPv4-Adresse", + "form_error_ip4_gateway_format": "Ungültige IPv4-Adresse des Gateways", "form_error_ip6_format": "Ungültige IPv6-Adresse", "form_error_ip_format": "Ungültige IP-Adresse", "form_error_mac_format": "Ungültige MAC-Adresse", "form_error_client_id_format": "Client-ID muss nur Zahlen, Kleinbuchstaben und Bindestriche enthalten", "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 (Null) sein", + "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Hostname", "dhcp_table_expires": "Gültig bis", "dhcp_warning": "Wenn Sie den DHCP-Server trotzdem aktivieren möchten, stellen Sie sicher, dass sich in Ihrem Netzwerk kein anderer aktiver DHCP-Server befindet. Andernfalls kann es bei angeschlossenen Geräten zu einem Ausfall des Internets kommen!", - "dhcp_error": "AdGuard Home konnte nicht ermitteln, ob es einen anderen aktiven DHCP-Server im Netzwerk gibt.", + "dhcp_error": "AdGuard Home konnte keinen anderen aktiven DHCP-Server im Netzwerk feststellen", "dhcp_static_ip_error": "Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Es konnte nicht ermittelt werden, ob diese Netzwerkschnittstelle mit statischer IP-Adresse konfiguriert ist. Bitte legen Sie eine statische IP-Adresse manuell fest.", "dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP-Server aktivieren“ klicken.", "dhcp_lease_added": "Statische Zuweisung „{{key}}“ erfolgreich hinzugefügt", @@ -149,9 +150,9 @@ "general_settings": "Allgemeine Einstellungen", "dns_settings": "DNS-Einstellungen", "dns_blocklists": "DNS-Sperrliste", - "dns_allowlists": "DNS-Freigabelisten", + "dns_allowlists": "DNS-Positivlisten", "dns_blocklists_desc": "AdGuard Home sperrt Domains, die in den Sperrlisten enthalten sind.", - "dns_allowlists_desc": "Domains aus DNS-Freigabelisten werden auch dann zugelassen, wenn sie in einer der Sperrlisten enthalten sind.", + "dns_allowlists_desc": "Domains aus DNS-Positivlisten werden auch dann zugelassen, wenn sie in einer der Sperrlisten enthalten sind.", "custom_filtering_rules": "Benutzerdefinierte Filterregeln", "encryption_settings": "Verschlüsselungseinstellungen", "dhcp_settings": "DHCP-Einstellungen", @@ -181,44 +182,47 @@ "elapsed": "Verstrichen", "filters_and_hosts_hint": "AdGuard Home versteht grundlegende Werbefilterregeln und Host-Datei-Syntax.", "no_blocklist_added": "Keine Sperrliste hinzugefügt", - "no_whitelist_added": "Keine Freigabeliste hinzugefügt", + "no_whitelist_added": "Keine Positivliste hinzugefügt", "add_blocklist": "Sperrliste hinzufügen", - "add_allowlist": "Freigabeliste hinzufügen", + "add_allowlist": "Positivliste hinzufügen", "cancel_btn": "Abbrechen", "enter_name_hint": "Name eingeben", "enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben", - "check_updates_btn": "Nach Updates suchen", + "check_updates_btn": "Nach Aktualisierungen suchen", "new_blocklist": "Neue Sperrliste", - "new_allowlist": "Neue Freigabeliste", + "new_allowlist": "Neue Positivliste", "edit_blocklist": "Sperrliste bearbeiten", - "edit_allowlist": "Freigabeliste bearbeiten", + "edit_allowlist": "Positivliste bearbeiten", "choose_blocklist": "Sperrliste wählen", - "choose_allowlist": "Freigabeliste wählen", + "choose_allowlist": "Positivliste wählen", "enter_valid_blocklist": "Gültige Webadresse zur Sperrliste eingeben.", - "enter_valid_allowlist": "Gültige Webadresse zur Freigabeliste eingeben.", + "enter_valid_allowlist": "Gültige Webadresse zur Positivliste eingeben.", "form_error_url_format": "Ungültiges URL-Format", "form_error_url_or_path_format": "Ungültige URL oder absoluter Pfad der Liste", "custom_filter_rules": "Benutzerdefinierte Filterregeln", "custom_filter_rules_hint": "Geben Sie pro Zeile eine Regel ein. Sie können entweder Werbefilterregeln oder Host-Datei-Syntax verwenden.", "system_host_files": "Hosts-Datei des Systems", "examples_title": "Beispiele", - "example_meaning_filter_block": "blockiert den Zugang zur Domain example.org und all ihren Subdomains", - "example_meaning_filter_whitelist": "entblockt den Zugang zur Domain example.org und all ihren Subdomains", - "example_meaning_host_block": "AdGuard Home wird jetzt die Adresse 127.0.0.1 für die Domain example.org zurückgeben (aber nicht für die Subdomains).", - "example_comment": "! Hier steht ein Kommentar", - "example_comment_meaning": "Nur ein Kommentar", - "example_comment_hash": "# Auch ein Kommentar", - "example_regex_meaning": "Zugriff auf die Domains sperren, die dem <0>angegebenen regulären Ausdruck entsprechen", - "example_upstream_regular": "regulärer DNS (über UDP)", - "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", - "example_upstream_sdns": "Sie können <0>DNS-Stempel für <1>DNSCrypt oder <2>DNS-over-HTTPS Resolver benutzen", - "example_upstream_tcp": "regulärer DNS (über TCP)", + "example_meaning_filter_block": "Zugriff auf die Domain example.org und alle ihre Subdomains sperren;", + "example_meaning_filter_whitelist": "Zugriff auf die Domain example.org und alle ihre Subdomains entsperren;", + "example_meaning_host_block": "Adresse 127.0.0.1 für die Domain example.org zurückgeben (aber nicht für ihre Subdomains);", + "example_comment": "! Hier steht ein Kommentar.", + "example_comment_meaning": "nur ein Kommentar;", + "example_comment_hash": "# Auch ein Kommentar.", + "example_regex_meaning": "Zugriff auf die Domains sperren, die dem angegebenen regulären Ausdruck entsprechen.", + "example_upstream_regular": "reguläres DNS (over UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Alle Listen sind bereits auf dem neuesten Stand", "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", @@ -261,8 +265,8 @@ "anonymize_client_ip": "Client-IP anonymisieren", "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern", "dns_config": "DNS-Serverkonfiguration", - "dns_cache_config": "Konfiguration des DNS-Zwischenspeicher", - "dns_cache_config_desc": "Hier können Sie den DNS-Zwischenspeicher konfigurieren", + "dns_cache_config": "Konfiguration des DNS-Cache", + "dns_cache_config_desc": "Hier können Sie den DNS-Cache konfigurieren", "blocking_mode": "Sperrmodus", "default": "Standard", "nxdomain": "NXDomain", @@ -272,8 +276,8 @@ "blocking_ipv4": "IPv4-Sperren", "blocking_ipv6": "IPv6-Sperren", "dnscrypt": "DNSCrypt", - "dns_over_https": "DNS-over-HTTPS (DNS-Abfrage über HTTPS)", - "dns_over_tls": "DNS-over-TLS (DNS-Abfrage über TLS)", + "dns_over_https": "DNS-over-HTTPS", + "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", "client_id": "Client-ID", "client_id_placeholder": "Client-ID eingeben", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Begrenzungswert eingeben", "rate_limit": "Begrenzungswert", "edns_enable": "EDNS Client Subnetz aktivieren", - "edns_cs_desc": "Senden Sie die Subnetze der Clients an die DNS-Server.", + "edns_cs_desc": "Die Option EDNS Client Subnetz (ECS) zu Upstream-Anfragen hinzufügen und die von Clients gesendeten Werte protokollieren.", "rate_limit_desc": "Die Anzahl der Anfragen pro Sekunde, die ein einzelner Client stellen darf. Das Setzen auf 0 bedeutet keine Begrenzung.", "blocking_ipv4_desc": "IP-Adresse, die für eine gesperrte A-Anfrage zurückgegeben werden soll", "blocking_ipv6_desc": "IP-Adresse, die für eine gesperrte AAAA-Anfrage zurückgegeben werden soll", @@ -309,7 +313,7 @@ "install_settings_listen": "Netzwerk-Schnittstelle\n", "install_settings_port": "Port", "install_settings_interface_link": "Ihre AdGuard Home Admin-Weboberfläche ist unter den folgenden Adressen verfügbar:", - "form_error_port": "Gültige Portnummer eingeben", + "form_error_port": "Geben Sie eine gültige Portnummer ein", "install_settings_dns": "DNS-Server", "install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:", "install_settings_all_interfaces": "Alle Schnittstellen", @@ -334,12 +338,12 @@ "install_devices_router_list_4": "Bei einigen Routertypen kann kein eigener DNS-Server eingerichtet werden. In diesem Fall kann es helfen, AdGuard Home als <0>DHCP-Server einzurichten. Andernfalls sollten Sie im Handbuch des Routers nachsehen, wie Sie DNS-Server auf Ihrem konkreten Router-Modell anpassen können.", "install_devices_windows_list_1": "Öffnen Sie die Systemsteuerung über das Startmenü oder die Windows-Suche.", "install_devices_windows_list_2": "Öffnen Sie die Kategorie „Netzwerk und Internet“ und dann „Netzwerk- und Freigabecenter“.", - "install_devices_windows_list_3": "Suchen Sie auf der linken Seite des Bildschirms nach „Adaptereinstellungen ändern“ und klicken Sie darauf.", - "install_devices_windows_list_4": "Wählen Sie Ihre aktive Verbindung aus, klicken Sie mit der rechten Maustaste darauf und wählen Sie „Eigenschaften“.", + "install_devices_windows_list_3": "Klicken Sie in der linken Leiste auf „Adaptereinstellungen ändern“.", + "install_devices_windows_list_4": "Klicken Sie mit der rechten Maustaste auf Ihre aktive Verbindung und wählen Sie „Eigenschaften“.", "install_devices_windows_list_5": "Suchen Sie in der Liste nach „Internet Protokoll Version 4 (TCP/IP)“ (oder, für IPv6, „Internet Protocol Version 6 (TCP/IPv6)“), markieren Sie diese und klicken Sie dann erneut auf „Eigenschaften“.", "install_devices_windows_list_6": "Wählen Sie „Folgende DNS-Serveradressen verwenden“ und geben Sie Ihre AdGuard Home-Serveradressen ein.", - "install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol (oben links in der Menüzeile) und wählen den Eintrag „Systemeinstellungen“.", - "install_devices_macos_list_2": "Klicken Sie dort auf „Netzwerk“", + "install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol und gehen Sie zu Systemeinstellungen.", + "install_devices_macos_list_2": "Klicken Sie auf „Netzwerk“.", "install_devices_macos_list_3": "Wählen Sie die erste Verbindung in Ihrer Liste aus und klicken Sie auf „Weitere Optionen“.", "install_devices_macos_list_4": "Wählen Sie den Tab „DNS“ und geben Sie dort Ihre AdGuard Home-Serveradressen ein.", "install_devices_android_list_1": "Tippen Sie auf dem Startbildschirm des Android-Menüs auf „Einstellungen“.", @@ -356,11 +360,11 @@ "open_dashboard": "Übersicht öffnen", "install_saved": "Erfolgreich gespeichert", "encryption_title": "Verschlüsselung", - "encryption_desc": "Verschlüsselungsunterstützung (HTTPS/TLS) für DNS- und Admin-Weboberfläche", + "encryption_desc": "Unterstützung von Verschlüsselung (HTTPS/QUIC/TLS) für DNS- und Admin-Weboberfläche", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -388,8 +392,8 @@ "encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?", "topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen.", "topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen.", - "form_error_port_range": "Port zwischen 80 und 65535 eingeben", - "form_error_port_unsafe": "Dies ist ein unsicherer Port", + "form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein", + "form_error_port_unsafe": "Unsicherer Port", "form_error_equal": "Sollten nicht übereinstimmen", "form_error_password": "Passwörter stimmen nicht überein", "reset_settings": "Einstellungen zurücksetzen", @@ -405,8 +409,8 @@ "update_failed": "Das automatische Aktualisieren ist fehlgeschlagen. Bitte folgen Sie den Schritten, um manuell zu aktualisieren.", "manual_update": "Bitte befolgen Sie diese Schritte, um manuell zu aktualisieren.", "processing_update": "Bitte warten Sie, AdGuard Home wird aktualisiert …", - "clients_title": "Clients", - "clients_desc": "Geräte einrichten, die mit AdGuard Home verbunden sind", + "clients_title": "Persistente Clients", + "clients_desc": "Datensätze persistenter Clients für Geräte konfigurieren, die mit AdGuard Home verbunden sind", "settings_global": "Allgemein", "settings_custom": "Benutzerdefiniert", "table_client": "Client", @@ -432,18 +436,18 @@ "clients_not_found": "Keine Clients gefunden", "client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?", "list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?", - "auto_clients_title": "Clients (Laufzeit)", - "auto_clients_desc": "Daten zu den Clients, die AdGuard Home verwenden, aber nicht in der Konfiguration gespeichert sind", + "auto_clients_title": "Laufzeit-Clients", + "auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen", "access_title": "Zugriffsrechte", - "access_desc": "Hier können Sie die Zugriffsregeln für den AdGuard Home DNS-Server konfigurieren.", + "access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren", "access_allowed_title": "Zugelassene Clients", - "access_allowed_desc": "Eine Liste von CIDRs, IP-Adressen oder Client-IDs. Wenn konfiguriert, akzeptiert AdGuard Home Anfragen von diesen Clients.", + "access_allowed_desc": "Eine Liste von CIDRs, IP-Adressen oder Client-IDs. Wenn diese Liste gefüllt ist, akzeptiert AdGuard Home nur Anfragen von diesen Clients.", "access_disallowed_title": "Nicht zugelassene Clients", - "access_disallowed_desc": "Eine Liste von CIDRs, IP-Adressen oder Client-IDs. Wenn konfiguriert, löscht AdGuard Home Anfragen von diesen Clients. Wenn erlaubte Clients konfiguriert sind, wird dieses Feld ignoriert.", + "access_disallowed_desc": "Eine Liste von CIDRs, IP-Adressen oder ClientIDs. Wenn diese Liste gefüllt ist, weist AdGuard Home Anfragen von diesen Clients zurück. Dieses Feld wird ignoriert, wenn es Einträge in der Liste „Zugelassene Clients“ gibt.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} Tag", "interval_days_plural": "{{count}} Tage", "domain": "Domain", + "ecs": "ECS", "punycode": "Punycode", "answer": "Antwort", "filter_added_successfully": "Der Filter wurde erfolgreich hinzugefügt", @@ -507,7 +512,7 @@ "filter_updated": "Der Filter wurde erfolgreich aktualisiert", "statistics_configuration": "Statistikkonfiguration", "statistics_retention": "Statistiken speichern", - "statistics_retention_desc": "Wenn Sie den Zeitraum verringern, werden einige Daten verloren gehen", + "statistics_retention_desc": "Wenn Sie den Intervallwert verringern, gehen einige Daten verloren", "statistics_clear": "Statistiken leeren", "statistics_clear_confirm": "Möchten Sie die Statistiken wirklich löschen?", "statistics_retention_confirm": "Möchten Sie wirklich die Aufbewahrung der Statistiken ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.", @@ -552,10 +557,10 @@ "autofix_warning_list": "Es werden folgende Aufgaben ausgeführt: <0>Deaktivieren des DNSStubListener-Systems <0>Festlegen der DNS-Server-Adresse auf 127.0.0.1 <0>Ersetzen des symbolischen Linkziels von /etc/resolv.conf auf /run/systemd/resolve/resolv.conf <0>Anhalten des DNSStubListener (systemseitig aufgelöster Dienst wird nachladen)", "autofix_warning_result": "Als Folge daraus werden alle DNS-Anforderungen von Ihrem System standardmäßig von AdGuardHome verarbeitet.", "tags_title": "Schlagwörter", - "tags_desc": "Sie können die Schlagwörter auswählen, die dem Client entsprechen. Die Schlagwörter können in die Filterregeln aufgenommen werden und erlauben Ihnen, sie genauer anzuwenden. <0>Mehr erfahren", + "tags_desc": "Sie können die Schlagwörter auswählen, die dem Client entsprechen. Die Schlagwörter können in die Filterregeln aufgenommen werden und erlauben Ihnen, sie genauer anzuwenden. <0>Mehr erfahren.", "form_select_tags": "Schlagwörter des Clients auswählen", "check_title": "Filterung überprüfen", - "check_desc": "Prüfen, ob der Hostname gefiltert wird", + "check_desc": "Prüfen, ob der Hostname gefiltert wird.", "check": "Prüfen", "form_enter_host": "Gerätenamen eingeben", "filtered_custom_rules": "Nach benutzerdefinierten Filterregeln gefiltert", @@ -597,18 +602,18 @@ "safe_search": "Sichere Suche", "blocklist": "Sperrliste", "milliseconds_abbreviation": "ms", - "cache_size": "Größe des Zwischenspeichers", - "cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)", - "cache_ttl_min_override": "TTL-Minimalwert überschreiben (in Sekunden)", - "cache_ttl_max_override": "TTL-Höchstwert überschreiben (in Sekunden)", - "enter_cache_size": "Größe des Zwischenspeichers eingeben", - "enter_cache_ttl_min_override": "TTL-Minimalwert eingeben", - "enter_cache_ttl_max_override": "TTL-Höchstwert eingeben", - "cache_ttl_min_override_desc": "Überschreibt den TTL-Minimalwert, der vom vorgeschalteten Server empfangen wurde. Dieser Wert darf nicht mehr als 3600 (Sek.) (≙ 1 Stunde) betragen.", - "cache_ttl_max_override_desc": "Überschreibt den TLL-Maximalwert, der vom vorgeschalteten Server empfangen wurde.", - "ttl_cache_validation": "Der minimale Cache des TTL-Wertes muss kleiner oder gleich dem maximalen Wert sein", - "cache_optimistic": "Optimistisches Zwischenspeichern", - "cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Zwischenspeicher antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.", + "cache_size": "Größe des Cache", + "cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes).", + "cache_ttl_min_override": "TTL-Minimalwert überschreiben", + "cache_ttl_max_override": "TTL-Höchstwert überschreiben", + "enter_cache_size": "Größe des Cache (Bytes) eingeben", + "enter_cache_ttl_min_override": "TTL-Minimalwert eingeben (in Sekunden)", + "enter_cache_ttl_max_override": "TTL-Höchstwert eingeben (in Sekunden)", + "cache_ttl_min_override_desc": "Kurze Time-to-Live-Werte (Sekunden) verlängern, die vom Upstream-Server beim Caching von DNS-Antworten empfangen werden.", + "cache_ttl_max_override_desc": "Maximalen Time-to-Live-Wert (Sekunden) für Einträge im DNS-Cache festlegen.", + "ttl_cache_validation": "Der Überschreibungswert für die minimale TTL muss kleiner oder gleich dem für die maximale TTL sein", + "cache_optimistic": "Optimistisches Caching", + "cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Cache antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.", "filter_category_general": "Allgemein", "filter_category_security": "Sicherheit", "filter_category_regional": "Regional", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home wird alle DNS-Abfragen von diesem Client verwerfen.", "filter_allowlist": "Warnhinweis: Durch diese Aktion wird außerdem die Regel „{{disallowed_rule}}“ aus der Liste der zugelassenen Clients ausgeschlossen.", "last_rule_in_allowlist": "Dieser Client kann nicht gesperrt werden, da das Ausschließen der Regel „{{disallowed_rule}}“ die Liste „Zugelassene Clients“ deaktivieren würde.", - "experimental": "Experimentell", "use_saved_key": "Zuvor gespeicherten Schlüssel verwenden", "parental_control": "Kindersicherung", "safe_browsing": "Internetsicherheit", - "served_from_cache": "{{value}} (aus dem Zwischenspeicher abgerufen)" + "served_from_cache": "{{value}} (aus dem Cache abgerufen)", + "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten" } diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 1559a24e..ca423562 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS servers", "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.", "local_ptr_title": "Private reverse DNS servers", - "local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve the hostnames of clients with private IP addresses, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.", + "local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve PTR requests for addresses in private IP ranges, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.", "local_ptr_default_resolver": "By default, AdGuard Home uses the following reverse DNS resolvers: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system.", "local_ptr_placeholder": "Enter one server address per line", @@ -35,24 +35,25 @@ "dhcp_config_saved": "DHCP configuration successfully saved", "dhcp_ipv4_settings": "DHCP IPv4 Settings", "dhcp_ipv6_settings": "DHCP IPv6 Settings", - "form_error_required": "Required field.", - "form_error_ip4_format": "Invalid IPv4 address.", - "form_error_ip4_range_start_format": "Invalid IPv4 address of the range start.", - "form_error_ip4_range_end_format": "Invalid IPv4 address of the range end.", - "form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway.", - "form_error_ip6_format": "Invalid IPv6 address.", - "form_error_ip_format": "Invalid IP address.", - "form_error_mac_format": "Invalid MAC address.", - "form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens.", - "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.", - "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.", - "greater_range_end_error": "Must be greater than range end.", - "subnet_error": "Addresses must be in one subnet.", - "gateway_or_subnet_invalid": "Subnet mask invalid.", + "form_error_required": "Required field", + "form_error_ip4_format": "Invalid IPv4 address", + "form_error_ip4_range_start_format": "Invalid IPv4 address of the range start", + "form_error_ip4_range_end_format": "Invalid IPv4 address of the range end", + "form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway", + "form_error_ip6_format": "Invalid IPv6 address", + "form_error_ip_format": "Invalid IP address", + "form_error_mac_format": "Invalid MAC address", + "form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens", + "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", + "greater_range_end_error": "Must be greater than range end", + "subnet_error": "Addresses must be in one subnet", + "gateway_or_subnet_invalid": "Subnet mask invalid", "dhcp_form_gateway_input": "Gateway IP", "dhcp_form_subnet_input": "Subnet mask", "dhcp_form_range_title": "Range of IP addresses", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Hostname", "dhcp_table_expires": "Expires", "dhcp_warning": "If you want to enable DHCP server anyway, make sure that there is no other active DHCP server in your network, as this may break the Internet connectivity for devices on the network!", - "dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network.", + "dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network", "dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. AdGuard Home failed to determine if this network interface is configured using a static IP address. Please set a static IP address manually.", "dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}. In order to use DHCP server, a static IP address must be set. Your current IP address is <0>{{ipAddress}}. AdGuard Home will automatically set this IP address as static if you press the \"Enable DHCP server\" button.", "dhcp_lease_added": "Static lease \"{{key}}\" successfully added", @@ -196,8 +197,8 @@ "choose_allowlist": "Choose allowlists", "enter_valid_blocklist": "Enter a valid URL to the blocklist.", "enter_valid_allowlist": "Enter a valid URL to the allowlist.", - "form_error_url_format": "Invalid URL format.", - "form_error_url_or_path_format": "Invalid URL or absolute path of the list.", + "form_error_url_format": "Invalid URL format", + "form_error_url_or_path_format": "Invalid URL or absolute path of the list", "custom_filter_rules": "Custom filtering rules", "custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.", "system_host_files": "System hosts files", @@ -210,15 +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", @@ -259,10 +265,10 @@ "query_log_strict_search": "Use double quotes for strict search", "query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost", "anonymize_client_ip": "Anonymize client IP", - "anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics.", + "anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics", "dns_config": "DNS server configuration", "dns_cache_config": "DNS cache configuration", - "dns_cache_config_desc": "Here you can configure DNS cache.", + "dns_cache_config_desc": "Here you can configure DNS cache", "blocking_mode": "Blocking mode", "default": "Default", "nxdomain": "NXDOMAIN", @@ -285,7 +291,7 @@ "form_enter_rate_limit": "Enter rate limit", "rate_limit": "Rate limit", "edns_enable": "Enable EDNS client subnet", - "edns_cs_desc": "Send clients' subnets to the DNS servers.", + "edns_cs_desc": "Add the EDNS Client Subnet option (ECS) to upstream requests and log the values sent by the clients in the query log.", "rate_limit_desc": "The number of requests per second allowed per client. Setting it to 0 means no limit.", "blocking_ipv4_desc": "IP address to be returned for a blocked A request", "blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request", @@ -309,7 +315,7 @@ "install_settings_listen": "Listen interface", "install_settings_port": "Port", "install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:", - "form_error_port": "Enter valid port number.", + "form_error_port": "Enter valid port number", "install_settings_dns": "DNS server", "install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:", "install_settings_all_interfaces": "All interfaces", @@ -356,19 +362,19 @@ "open_dashboard": "Open Dashboard", "install_saved": "Saved successfully", "encryption_title": "Encryption", - "encryption_desc": "Encryption (HTTPS/TLS) support for both DNS and admin web interface.", + "encryption_desc": "Encryption (HTTPS/QUIC/TLS) support for both DNS and admin web interface", "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.", @@ -378,26 +384,26 @@ "encryption_key_input": "Copy/paste your PEM-encoded private key for your certificate here.", "encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)", "encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.", - "encryption_chain_valid": "Certificate chain is valid.", - "encryption_chain_invalid": "Certificate chain is invalid.", - "encryption_key_valid": "This is a valid {{type}} private key.", - "encryption_key_invalid": "This is an invalid {{type}} private key.", + "encryption_chain_valid": "Certificate chain is valid", + "encryption_chain_invalid": "Certificate chain is invalid", + "encryption_key_valid": "This is a valid {{type}} private key", + "encryption_key_invalid": "This is an invalid {{type}} private key", "encryption_subject": "Subject", "encryption_issuer": "Issuer", "encryption_hostnames": "Hostnames", "encryption_reset": "Are you sure you want to reset encryption settings?", "topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings.", "topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings.", - "form_error_port_range": "Enter port number in the range of 80-65535.", - "form_error_port_unsafe": "This is an unsafe port.", - "form_error_equal": "Must not be equal.", - "form_error_password": "Password mismatched.", + "form_error_port_range": "Enter port number in the range of 80-65535", + "form_error_port_unsafe": "Unsafe port", + "form_error_equal": "Must not be equal", + "form_error_password": "Password mismatch", "reset_settings": "Reset settings", "update_announcement": "AdGuard Home {{version}} is now available! <0>Click here for more info.", "setup_guide": "Setup Guide", "dns_addresses": "DNS addresses", "dns_start": "DNS server is starting up", - "dns_status_error": "Error checking the DNS server status.", + "dns_status_error": "Error checking the DNS server status", "down": "Down", "fix": "Fix", "dns_providers": "Here is a <0>list of known DNS providers to choose from.", @@ -406,7 +412,7 @@ "manual_update": "Please follow these steps to update manually.", "processing_update": "Please wait, AdGuard Home is being updated", "clients_title": "Persistent clients", - "clients_desc": "Configure persistent client records for devices connected to AdGuard Home.", + "clients_desc": "Configure persistent client records for devices connected to AdGuard Home", "settings_global": "Global", "settings_custom": "Custom", "table_client": "Client", @@ -433,9 +439,9 @@ "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "list_confirm_delete": "Are you sure you want to delete this list?", "auto_clients_title": "Runtime clients", - "auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home.", + "auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home", "access_title": "Access settings", - "access_desc": "Here you can configure access rules for the AdGuard Home DNS server.", + "access_desc": "Here you can configure access rules for the AdGuard Home DNS server", "access_allowed_title": "Allowed clients", "access_allowed_desc": "A list of CIDRs, IP addresses, or ClientIDs. If this list has entries, AdGuard Home will accept requests only from these clients.", "access_disallowed_title": "Disallowed clients", @@ -443,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", @@ -475,8 +481,8 @@ "dns_rewrites": "DNS rewrites", "form_domain": "Enter domain name or wildcard", "form_answer": "Enter IP address or domain name", - "form_error_domain_format": "Invalid domain format.", - "form_error_answer_format": "Invalid answer format.", + "form_error_domain_format": "Invalid domain format", + "form_error_answer_format": "Invalid answer format", "configure": "Configure", "main_settings": "Main settings", "block_services": "Block specific services", @@ -500,6 +506,7 @@ "interval_days": "{{count}} day", "interval_days_plural": "{{count}} days", "domain": "Domain", + "ecs": "ECS", "punycode": "Punycode", "answer": "Answer", "filter_added_successfully": "The list has been successfully added", @@ -507,7 +514,7 @@ "filter_updated": "The list has been successfully updated", "statistics_configuration": "Statistics configuration", "statistics_retention": "Statistics retention", - "statistics_retention_desc": "If you decrease the interval value, some data will be lost.", + "statistics_retention_desc": "If you decrease the interval value, some data will be lost", "statistics_clear": "Clear statistics", "statistics_clear_confirm": "Are you sure you want to clear statistics?", "statistics_retention_confirm": "Are you sure you want to change statistics retention? If you decrease the interval value, some data will be lost", @@ -517,7 +524,7 @@ "interval_hours_plural": "{{count}} hours", "filters_configuration": "Filters configuration", "filters_enable": "Enable filters", - "filters_interval": "Filters update interval", + "filters_interval": "Filter update interval", "disabled": "Disabled", "username_label": "Username", "username_placeholder": "Enter username", @@ -606,7 +613,7 @@ "enter_cache_ttl_max_override": "Enter maximum TTL (seconds)", "cache_ttl_min_override_desc": "Extend short time-to-live values (seconds) received from the upstream server when caching DNS responses.", "cache_ttl_max_override_desc": "Set a maximum time-to-live value (seconds) for entries in the DNS cache.", - "ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum.", + "ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum", "cache_optimistic": "Optimistic caching", "cache_optimistic_desc": "Make AdGuard Home respond from the cache even when the entries are expired and also try to refresh them.", "filter_category_general": "General", @@ -628,5 +635,5 @@ "parental_control": "Parental Control", "safe_browsing": "Safe Browsing", "served_from_cache": "{{value}} (served from cache)", - "form_error_password_length": "Password must be at least {{value}} characters long." + "form_error_password_length": "Password must be at least {{value}} characters long" } diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 753330ac..895d0c33 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -1,7 +1,7 @@ { "client_settings": "Configuración de clientes", - "example_upstream_reserved": "puedes especificar el DNS de subida <0>para un dominio específico", - "example_upstream_comment": "puedes especificar el comentario", + "example_upstream_reserved": "un DNS de subida <0>para un dominio específico.", + "example_upstream_comment": "un comentario.", "upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.", "parallel_requests": "Consultas paralelas", "load_balancing": "Balanceo de carga", @@ -9,7 +9,7 @@ "bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como DNS de subida.", "local_ptr_title": "Servidores DNS inversos y privados", - "local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver los nombres de hosts de los clientes a direcciones IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.", + "local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.", "local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.", "local_ptr_placeholder": "Ingresa una dirección de servidor por línea", @@ -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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nombre del host", "dhcp_table_expires": "Expira", "dhcp_warning": "Si de todos modos deseas habilitar el servidor DHCP, asegúrate de que no hay otro servidor DHCP activo en tu red. ¡De lo contrario, puedes dejar sin conexión a Internet a los dispositivos conectados!", - "dhcp_error": "AdGuard Home no pudo determinar si hay otro servidor DHCP activo en la red.", + "dhcp_error": "AdGuard Home no pudo determinar si hay otro servidor DHCP activo en la red", "dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. AdGuard Home no pudo determinar si esta interfaz de red está configurada utilizando una dirección IP estática. Por favor establece una dirección IP estática manualmente.", "dhcp_dynamic_ip_found": "Tu sistema utiliza la configuración de dirección IP dinámica para la interfaz <0>{{interfaceName}}. Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. Tu dirección IP actual es <0>{{ipAddress}}. AdGuard Home establecerá automáticamente esta dirección IP como estática si presionas el botón \"Habilitar servidor DHCP\".", "dhcp_lease_added": "Asignación estática \"{{key}}\" añadido correctamente", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Ingresa una regla por línea. Puedes utilizar reglas de bloqueo o la sintaxis de los archivos hosts.", "system_host_files": "Archivos hosts del sistema", "examples_title": "Ejemplos", - "example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org y a todos sus subdominios", - "example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios", - "example_meaning_host_block": "AdGuard Home devolverá la dirección 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).", - "example_comment": "! Aquí va un comentario", - "example_comment_meaning": "solo un comentario", - "example_comment_hash": "# También un comentario", - "example_regex_meaning": "bloquea el acceso a los dominios que coincidan con la expresión regular especificada", - "example_upstream_regular": "DNS regular (mediante UDP)", - "example_upstream_dot": "cifrado <0>DNS mediante TLS", - "example_upstream_doh": "cifrado <0>DNS mediante HTTPS", - "example_upstream_doq": "cifrado <0>DNS mediante QUIC", - "example_upstream_sdns": "puedes usar <0>DNS Stamps para <1>DNSCrypt o resolutores <2>DNS mediante HTTPS", - "example_upstream_tcp": "DNS regular (mediante TCP)", + "example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org y a todos sus subdominios.", + "example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios.", + "example_meaning_host_block": "responde con 127.0.0.1 para ejemplo.org (pero no para sus subdominios).", + "example_comment": "! Aquí va un comentario.", + "example_comment_meaning": "solo un comentario.", + "example_comment_hash": "# También un comentario.", + "example_regex_meaning": "bloquea el acceso a los dominios que coincidan con la expresión regular especificada.", + "example_upstream_regular": "DNS regular (mediante UDP).", + "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.", + "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).", "all_lists_up_to_date_toast": "Todas las listas ya están actualizadas", "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", @@ -259,7 +263,7 @@ "query_log_strict_search": "Usar comillas dobles para una búsqueda estricta", "query_log_retention_confirm": "¿Estás seguro de que deseas cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos", "anonymize_client_ip": "Anonimizar IP del cliente", - "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros y estadísticas", + "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas", "dns_config": "Configuración del servidor DNS", "dns_cache_config": "Configuración de la caché DNS", "dns_cache_config_desc": "Aquí puedes configurar la caché DNS", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS mediante QUIC", "client_id": "ID de cliente", "client_id_placeholder": "Ingresa el ID del cliente", - "client_id_desc": "Diferentes clientes pueden ser identificados por un ID de cliente especial. Aquí puedes obtener más información sobre cómo identificar clientes.", + "client_id_desc": "Los clientes pueden ser identificados por un ID de cliente. Obtén más información sobre cómo identificar clientes aquí.", "download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS", "download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS", "download_mobileconfig": "Descargar archivo de configuración", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Ingresa el límite de cantidad", "rate_limit": "Límite de cantidad", "edns_enable": "Habilitar subred de cliente EDNS", - "edns_cs_desc": "Envía las subredes de los clientes a los servidores DNS.", + "edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del DNS de subida y registra los valores enviados por los clientes en el registro de consultas.", "rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.", "blocking_ipv4_desc": "Dirección IP devolverá una petición A bloqueada", "blocking_ipv6_desc": "Dirección IP devolverá una petición AAAA bloqueada", @@ -334,8 +338,8 @@ "install_devices_router_list_4": "En algunos tipos de router, no se puede configurar un servidor DNS personalizado. En ese caso, configurar AdGuard Home como <0>servidor DHCP puede ayudar. De lo contrario, debes consultar el manual del router para saber cómo personalizar los servidores DNS en tu modelo de router específico.", "install_devices_windows_list_1": "Abre el Panel de control a través del menú Inicio o en el buscador de Windows.", "install_devices_windows_list_2": "Ve a la categoría Redes e Internet, luego a Centro de redes y recursos compartidos.", - "install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busca \"Cambiar configuración del adaptador\" y luego haz clic en él.", - "install_devices_windows_list_4": "Selecciona tu conexión activa, haz clic derecho sobre ella y elige Propiedades.", + "install_devices_windows_list_3": "En el panel izquierdo, haz clic en \"Cambiar configuración del adaptador\".", + "install_devices_windows_list_4": "Haz clic derecho en tu conexión activa y selecciona Propiedades.", "install_devices_windows_list_5": "Busca en la lista el \"Protocolo de Internet versión 4 (TCP/IPv4)\" (o \"Protocolo de Internet versión 6 (TCP/IPv6)\"), selecciónalo y vuelve a hacer clic en Propiedades.", "install_devices_windows_list_6": "Elige \"Usar las siguientes direcciones de servidor DNS\" e ingresa las direcciones de tu servidor AdGuard Home.", "install_devices_macos_list_1": "Haz clic en el icono de Apple y ve a Preferencias del sistema.", @@ -349,18 +353,18 @@ "install_devices_android_list_5": "Cambia los valores de DNS 1 y DNS 2 a las direcciones de tu servidor AdGuard Home.", "install_devices_ios_list_1": "En la pantalla de inicio, pulsa en Configuración.", "install_devices_ios_list_2": "Elige Wi-Fi en el menú de la izquierda (es imposible configurar DNS para redes móviles).", - "install_devices_ios_list_3": "Pulsa sobre el nombre de la red activa en ese momento.", + "install_devices_ios_list_3": "Pulsa sobre el nombre de la red actualmente activa.", "install_devices_ios_list_4": "En el campo DNS ingresa las direcciones de tu servidor AdGuard Home.", "get_started": "Comenzar", "next": "Siguiente", "open_dashboard": "Abrir panel de control", "install_saved": "Guardado correctamente", "encryption_title": "Cifrado", - "encryption_desc": "Soporte de cifrado (HTTPS/TLS) tanto para DNS como para la interfaz web de administración", + "encryption_desc": "Soporte de cifrado (HTTPS/QUIC/TLS) tanto para DNS como para la interfaz web de administración", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado.", "topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado.", "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535", - "form_error_port_unsafe": "Este es un puerto inseguro", + "form_error_port_unsafe": "Puerto inseguro", "form_error_equal": "No debe ser igual", "form_error_password": "La contraseña no coincide", "reset_settings": "Restablecer configuración", @@ -405,8 +409,8 @@ "update_failed": "Error en la actualización automática. Por favor sigue estos pasos para actualizar manualmente.", "manual_update": "Por favor sigue estos pasos para actualizar manualmente.", "processing_update": "Por favor espera, AdGuard Home se está actualizando", - "clients_title": "Clientes", - "clients_desc": "Configurar dispositivos conectados con AdGuard Home", + "clients_title": "Clientes persistentes", + "clients_desc": "Configurar registros de clientes persistentes para dispositivos conectados a AdGuard Home", "settings_global": "Global", "settings_custom": "Personalizado", "table_client": "Cliente", @@ -417,7 +421,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Dirección IP", - "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un ID de cliente especial (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí puedes obtener más información sobre cómo identificar clientes.", + "client_identifier_desc": "Los clientes pueden ser identificados por su dirección IP, MAC, CIDR o un ID de cliente (puede ser utilizado para DoT/DoH/DoQ). Obtén más información sobre cómo identificar clientes <0>aquí.", "form_enter_ip": "Ingresa la IP", "form_enter_subnet_ip": "Ingresa una dirección IP en la subred \"{{cidr}}\"", "form_enter_mac": "Ingresa la MAC", @@ -432,18 +436,18 @@ "clients_not_found": "No se han encontrado clientes", "client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?", "list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?", - "auto_clients_title": "Clientes (activos)", - "auto_clients_desc": "Datos de los clientes que utilizan AdGuard Home, pero no se almacenan en la configuración", + "auto_clients_title": "Clientes activos", + "auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home", "access_title": "Configuración de acceso", - "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home.", + "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home", "access_allowed_title": "Clientes permitidos", - "access_allowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si está configurado, AdGuard Home aceptará peticiones solo de estos clientes.", + "access_allowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si esta lista tiene entradas, AdGuard Home aceptará peticiones solo de estos clientes.", "access_disallowed_title": "Clientes no permitidos", - "access_disallowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si está configurado, AdGuard Home descartará las peticiones de estos clientes. Si se configuran clientes permitidos, este campo será ignorado.", + "access_disallowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si esta lista tiene entradas, AdGuard Home descartará las peticiones de estos clientes. Este campo será ignorado si hay entradas en clientes permitidos.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} día", "interval_days_plural": "{{count}} días", "domain": "Dominio", + "ecs": "ECS", "punycode": "Punycode", "answer": "Respuesta", "filter_added_successfully": "La lista ha sido añadida correctamente", @@ -532,7 +537,7 @@ "netname": "Nombre de la red", "network": "Red", "descr": "Descripción", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Más información sobre cómo crear tus propias listas de hosts.", "blocked_by_response": "Bloqueado por CNAME o IP en respuesta", "blocked_by_cname_or_ip": "Bloqueado por CNAME o IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener <0>Establecer la dirección del servidor DNS en 127.0.0.1 <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf <0>Detener DNSStubListener (recargar el servicio systemd-resolved)", "autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.", "tags_title": "Etiquetas", - "tags_desc": "Puedes seleccionar las etiquetas que correspondan al cliente. Las etiquetas pueden ser incluidas en las reglas de filtrado y te permiten aplicarlas con mayor precisión. <0>Más información", + "tags_desc": "Puedes seleccionar las etiquetas que correspondan al cliente. Incluye etiquetas en las reglas de filtrado para aplicarlas con mayor precisión. <0>Más información.", "form_select_tags": "Seleccione las etiquetas del cliente", "check_title": "Comprobar filtrado", - "check_desc": "Comprueba si el nombre del host está siendo filtrado", + "check_desc": "Comprueba si un nombre del host está siendo filtrado.", "check": "Comprobar", "form_enter_host": "Ingresa un nombre de host", "filtered_custom_rules": "Filtrado por reglas de filtrado personalizado", @@ -598,15 +603,15 @@ "blocklist": "Lista de bloqueo", "milliseconds_abbreviation": "ms", "cache_size": "Tamaño de la caché", - "cache_size_desc": "Tamaño de la caché DNS (en bytes)", + "cache_size_desc": "Tamaño de la caché DNS (en bytes).", "cache_ttl_min_override": "Anular TTL mínimo", "cache_ttl_max_override": "Anular TTL máximo", "enter_cache_size": "Ingresa el tamaño de la caché (bytes)", "enter_cache_ttl_min_override": "Ingresa el TTL mínimo (en segundos)", "enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)", - "cache_ttl_min_override_desc": "Amplia el corto tiempo de vida de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS", - "cache_ttl_max_override_desc": "Establece un valor de tiempo de vida máximo para las entradas en la caché DNS", - "ttl_cache_validation": "El valor TTL mínimo de la caché debe ser menor o igual al valor máximo", + "cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS.", + "cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.", + "ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo", "cache_optimistic": "Caché optimista", "cache_optimistic_desc": "Haz que AdGuard Home responda desde la caché incluso cuando las entradas estén expiradas y también intente actualizarlas.", "filter_category_general": "General", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home descartará todas las consultas DNS de este cliente.", "filter_allowlist": "ADVERTENCIA: Esta acción también excluirá la regla \"{{disallowed_rule}}\" de la lista de clientes permitidos.", "last_rule_in_allowlist": "No se puede desautorizar a este cliente porque al excluir la regla \"{{disallowed_rule}}\" DESHABILITARÁ la lista de \"Clientes permitidos\".", - "experimental": "experimental", "use_saved_key": "Usar la clave guardada previamente", "parental_control": "Control parental", "safe_browsing": "Navegación segura", - "served_from_cache": "{{value}} (servido desde la caché)" + "served_from_cache": "{{value}} (servido desde la caché)", + "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres" } diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json index 22e78275..aef9e5d3 100644 --- a/client/src/__locales/fa.json +++ b/client/src/__locales/fa.json @@ -1,26 +1,49 @@ { "client_settings": "تنظیمات کلاینت", + "example_upstream_reserved": "میتوانید جریان ارسالی DNS <0> را برای یک دامنه مشخص تعیین کنید ", + "upstream_parallel": "استفاده از جستار موازی برای سرعت دادن به تفکیک با جستار همزمان همه جریان های ارسالی", "parallel_requests": "درخواست های موازی", "bootstrap_dns": "خودراه انداز سرورهای DNS", "bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.", + "local_ptr_title": "سرورهای خصوصی DNS", + "local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.", + "local_ptr_no_default_resolver": "AdGuard Home نتوانست برای این دستگاه تعیین کننده های DNS معکوس محرمانه مناسب را معین کند.", + "local_ptr_placeholder": "در هر خط یک آدرس سرور را وارد کنید", + "resolve_clients_title": "فعال سازی تعیین نام های سرویس دهنده ی سرویس گیرندگان", + "resolve_clients_desc": "در صورت فعال بودن،AdGuard Home به طور خودکار اقدام به تعیین نام های سرویس دهنده ی سرویس گیرندگان از آدرس های آی پی با ارسال یک درخواست PTR به یک تعیین کننده ی همتا خواهد کرد (سرور خصوصی DNS برای سرویس گیرندگان محلی،سرور مادر برای سرویس گیرندگان با آی پی عمومی).", + "use_private_ptr_resolvers_title": "از تعیین کننده های rDNS محرمانه استفاده کنید", + "use_private_ptr_resolvers_desc": "داده گیری های دی ان اس معکوس را برای آدرس های اینترنتی خدمات منظقه ای با استفاده از سرور های مادر اجرا کنید. چنانچه غیر فعال باشد،AdGuard Home با NXDOMAIN به همه چنین درخواست های PTR پاسخ می دهد به استثناء خدمات گیرنده های شناخته شده از طرف DHCP،/و غیره/هوست ها، و سایر.", "check_dhcp_servers": "بررسی برای سرورهای DHCP", + "save_config": "ذخیره پیکربندی", "enabled_dhcp": "سرور DHCP فعال شده است", "disabled_dhcp": "سرور DHCP غیرفعال شده است", "dhcp_title": "سرور DHCP", "dhcp_description": "اگر روتر شما تنظیمات DHCP ارائه نمی کند،میتوانید از سرور DHCP تو-کار خود AdGuard استفاده کنید.", "dhcp_enable": "فعالسازی سرور DHCP", "dhcp_disable": "غیرفعالسازی سرور DHCP", + "dhcp_not_found": "سرورهای فعال DHCP در شبکه یافت نشد.فعالسازی سرور DHCP تو-کار اَمن است.", "dhcp_found": "تعدادی سرور DHCP فعال در شبکه یافت شد.فعالسازی سرور DHCP توکار اَمن نیست.", "dhcp_leases": "اجاره DHCP", "dhcp_static_leases": "اجاره DHCP ایستا", "dhcp_leases_not_found": "اجاره DHCP یافت نشد", + "dhcp_config_saved": "پیکربندی سرور DHCP ذخیره شده است", "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": "آدرس آی پی نامعتبر است", + "form_error_ip_format": "فرمت IPv4 نامعتبر است", "form_error_mac_format": "فرمت مَک نامعتبر است", "form_error_client_id_format": "فرمت شناسه کلاینت نامعتبر است", + "form_error_subnet": "زیرشبکه\"{{cidr}}\"آدرس آی پی {{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": "آی پی دروازه", "dhcp_form_subnet_input": "ماسک زیر شبکه", "dhcp_form_range_title": "دامنه آدرس های آی پی", @@ -34,11 +57,19 @@ "ip": "IP", "dhcp_table_hostname": "نام میزبان", "dhcp_table_expires": "انقضاء", + "dhcp_warning": "اگر میخواهید DHCP سرور توکار را فعال کنید،مطمئن شوید DHCP سرور دیگری فعال نباشد.در غیر اینصورت،آن دسترسی به اینترنت را برای دستگاه های وصل شده قطع می کند!", + "dhcp_error": "ما نمیتوانیم تشخیص دهیم آیا یک سرور DHCP دیگر در شبکه موجود است یا نه.", + "dhcp_static_ip_error": "به منظور استفاده از سرور DHCP یک آدرس آی پی ثابت باید تنظیم شود.ما موفق به تشخیص اینکه آیا رابط این شبکه برای استفاده از آی پی ثابت تنظیم شده است یا نه موفق نشدیم.لطفا آدرس آی پی ثابت را دستی تنظیم کنید.", + "dhcp_dynamic_ip_found": "سیستم شما آدرس آی پی متغییر برای این رابط استفاده می کند <0>{{interfaceName}}. به منظور استفاده از سرور DHCP آدرس ثابت باید تعیین شود. آدرس آی پی فعلی شما هست <0>{{ipAddress}}. اگر شما دکمه DHCP را فشار دهید ما این آدرس آی پی را بعنوان ثابت تنظیم می کنیم.", "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}}\" را حذف کنید؟", @@ -54,6 +85,7 @@ "filters": "فيلترها", "filter": "فیلتر", "query_log": "جستار وقایع", + "compact": "فشرده", "nothing_found": "هیچ چیز یافت نشد", "faq": "پرسش و پاسخ", "version": "نسخه", @@ -78,13 +110,22 @@ "for_last_24_hours": "برای 24 ساعت گذشته", "for_last_days": "برای {{count}} روز آخر", "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 مسدود شده با فیلترهای مسدودساز تبلیغ و لیست سیاه میزبان", + "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": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان", @@ -94,6 +135,7 @@ "use_adguard_parental": "از سرویس وب نظارت والدین AdGuard استفاده کن", "use_adguard_parental_hint": "AdGuard Home بررسی می کند اگر دامنه حاوی موارد غیر اخلاقی است.آن از همان اِی پی آی دارای حریم خصوصی سرویس وب امنیت وب گردی استفاده می کند.", "enforce_safe_search": "اجبار جستجوی اَمن", + "enforce_save_search_hint": "AdGuard Home میتواند جستجوی اَمن را در موتور جستجوهای زیر اعمال کند:گوگل،یوتوب،بینگ و یاندکس", "no_servers_specified": "سروری تعیین نشده است", "general_settings": "تنظیمات عمومی", "dns_settings": "تنظیمات DNS", @@ -145,6 +187,7 @@ "form_error_url_or_path_format": "آدرس نامعتبر یا یک مسیر کامل لیست", "custom_filter_rules": "دستورات فیلترینگ دستی", "custom_filter_rules_hint": "یک دستور در خط وارد کنید.میتوانید از دستورات مسدودساز تبلیغ یا نحو فایل های میزبان استفاده کنید.", + "system_host_files": "فایل های هوست سیستم", "examples_title": "مثال ها", "example_meaning_filter_block": "مسدودسازی دسترسی به دامنه example.org و همه زیر دامنه ها آن", "example_meaning_filter_whitelist": "بازکردن دسترسی به دامنه example.org و همه زیر دامنه ها آن", @@ -159,6 +202,7 @@ "example_upstream_sdns": "شما میتوانید از DNS Stamps برای DNSCrypt یا DNS-over-HTTPS resolvers استفاده کنید", "example_upstream_tcp": "DNS عادی (بر TCP)", "all_lists_up_to_date_toast": "همه لیست ها از قبل بروز اند", + "updated_upstream_dns_toast": "سرورهای DNS جریان ارسالی بروز رسانی شده است", "dns_test_ok_toast": "سرورهای DNS تعیین شده بدرستی کار می کنند", "dns_test_not_ok_toast": "سرور \"{{key}}\": نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید", "unblock": "رفع انسداد", @@ -169,6 +213,7 @@ "domain_or_client": "دامنه یا کلاینت", "type_table_header": "نوع", "response_table_header": "پاسخ", + "response_code": "کد پاسخ", "client_table_header": "کلاینت", "empty_response_status": "خالی", "show_all_filter_type": "نمایش همه", @@ -187,6 +232,7 @@ "query_log_filtered": "فیلتر شده با {{filter}}", "query_log_confirm_clear": "آیا واقعا میخواهید کل وقایع جستار را پاک کنید؟", "query_log_cleared": "وقایع جستار با موفقیت پاک شد", + "query_log_updated": "فیلتر با موفقیت بروز رسانی شد", "query_log_clear": "پاکسازی وقایع جستار", "query_log_retention": "حفظ وقایع جستار برای", "query_log_enable": "فعالسازی وقایع", @@ -212,15 +258,19 @@ "rate_limit": "میزان محدودیت", "edns_enable": "فعالسازی زیرشبکه کلاینت EDNS", "edns_cs_desc": "اگر فعال باشد،AdGuard Home زیرشبکه های کلاینت ها را به سرورهای DNS می فرستد.", + "rate_limit_desc": "تعداد درخواست های بر ثانیه مجازی که یک کلاینت میتواند بسازد (0: نامحدود)", "blocking_ipv4_desc": "آدرس آی پی برگشت داده شده برای درخواست مسدود شده A", "blocking_ipv6_desc": "آدرس آی پی برگشت داده شده برای درخواست مسدود شده AAAA", + "blocking_mode_default": "پیش فرض: وقتی مسدود شود با دستور سبک-مسدودساز تبلیغ REFUSED پاسخ میدهد،پاسخ با آدرس آی پی تعیین شده در دستور وقتی با دستور /etc/hosts-style rule مسدود شود", "blocking_mode_nxdomain": "NXDOMAIN: پاسخ با کُد NXDOMAIN", "blocking_mode_null_ip": "Null IP: پاسخ با آدرس آی پی صفر(0.0.0.0 برای A; :: برای AAAA)", "blocking_mode_custom_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": "ردیاب های شناخته شده", @@ -230,6 +280,7 @@ "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": "همه رابط ها", @@ -248,8 +299,10 @@ "install_devices_router": "روتر", "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 بگردید در فیلدی که اجازه دو یا سه گروه عدد را میدهد و هر کدام در چهار گروه سه عددی شکسته شده است", "install_devices_router_list_3": "آدرس سرور AdGuard Home خود را آنجا وارد کنید", + "install_devices_router_list_4": "شما نمیتوانید DNS سرور سفارشی در برخی از روترها تنظیم کنید. در این مورد اگر شما AdGuard Home را بعنوان DHCP سرور راه اندازی کنید میتواند کمک کند. در غیر اینصورت باید راهنمای سفارشی سازی DNS سرورها برای مدل خاص روتر خود را انتخاب کنید.", "install_devices_windows_list_1": "کنترل پنل را از طریق استارت منو یا جستجوی ویندوز باز کنید.", "install_devices_windows_list_2": "بروید به شبکه و دسته اینترنت و سپس به شبکه و مرکز اشتراک گذاری", "install_devices_windows_list_3": "در سمت چپ صفحه تنظیمات آداپتور را تغییر داده و روی آن کلیک کنید", @@ -267,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": "بعدی", @@ -275,8 +327,10 @@ "install_saved": "با موفقیت ذخیره نشد", "encryption_title": "رمزگُذاری", "encryption_desc": "پشتیبانی رمزگُذاری (HTTPS/TLS) برای DNS و رابط آدمین وب", + "encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد", "encryption_server": "نام سرور", "encryption_server_enter": "نام دامنه خود را وارد کنید", + "encryption_server_desc": "به منظور استفاده از HTTPS،شما باید نام سرور مطابق با گواهینامه اِس اِس اِل را وارد کنید.", "encryption_redirect": "تغییر مسیر خودکار به HTTPS", "encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home خودکار شما را از آدرس HTTP به HTTPS منتقل می کند", "encryption_https": "پورت HTTPS", @@ -302,10 +356,13 @@ "encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟", "topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است. <0>تنظیمات رمزگُذاری را بروز رسانی کنید.", "topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <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", @@ -314,6 +371,7 @@ "dns_providers": "در اینجا یک <0>لیست از سرویس های ارائه دهنده DNS برای انتخاب هست.", "update_now": "حالا بروز رسانی", "update_failed": "بروز رسانی خودکار موفق نشد. لطفا مراحل را دنبال کرده تا بطور دستی بروز رسانی کنید.", + "manual_update": "لطفااین مراحل را دنبال کنیدتا به طور دستی بروزرسانی نمایید.", "processing_update": "منتظر بمانید،AdGuard Home در حال بروز رسانی است", "clients_title": "کلاینت ها", "clients_desc": "پیکربندی دستگاه های متصل شده به AdGuard Home", @@ -327,7 +385,9 @@ "client_edit": "ویرایش کلاینت", "client_identifier": "احراز با", "ip_address": "آدرس آی پی", + "client_identifier_desc": "کلاینت میتواند با آدرس آی پی یا آدرس مَک احراز شود. لطفا توجه کنید،که استفاده از مَک بعنوان عامل احراز زمانی امکان دارد که AdGuard Home نیز <0>سرور DHCP باشد", "form_enter_ip": "آی پی را وارد کنید", + "form_enter_subnet_ip": "یک آدرس آی پی در زیر شبکه \"{{cidr}}\" وارد کنید", "form_enter_mac": "مَک را وارد کنید", "form_enter_id": "خطای احرازکننده", "form_add_id": "افزودن احرازکننده", @@ -349,13 +409,15 @@ "access_disallowed_title": "کلاینت های غیرمجاز", "access_disallowed_desc": "یک لیست از CIDR یا آدرس های IP.اگر پیکربندی شود،AdGuard Home درخواست ها را از این آدرس های IP نمی پذیرد.", "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", "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>لطفا توجه کنید که پروتکل های رمزگذاری شده DNS فقط در آندروئید 9 پشتیبانی می شود. پس برای سیستم عامل های دیگر نیاز است که برنامه دیگری نصب کنید.<0>در اینجا میتوانید لیست نرم افزارهای قابل استفاده را ببینید.", "setup_dns_privacy_android_1": "آندروئید 9 بطور پیش فرض از DNS-over-TLS پشتیبانی می کند. برای پیکربندی آن، بروید به تنظیمات → شبکه & اینترنت → پیشرفته → DNS خصوصی و نام دامنه را آنجا وارد کنید.", "setup_dns_privacy_android_2": "<0>AdGuard for Android پشتیبانی از <1>DNS-over-HTTPS و <1>DNS-over-TLS.", "setup_dns_privacy_android_3": "<0>Intra قابلیت <1>DNS-over-HTTPS را به آندروئید اضافه می کند.", @@ -410,10 +472,11 @@ "statistics_configuration": "پیکربندی آمارها", "statistics_retention": "مدت حفظ آمارها", "statistics_retention_desc": "اگر مقدار فاصله را کاهش دهید،برخی داده ها از بین خواهد رفت", - "statistics_clear": "بازنشانی آمار", + "statistics_clear": " پاکسازی آمار", "statistics_clear_confirm": "آیا واقعا میخواهید آمار را پاک کنید؟", "statistics_retention_confirm": "آیا واقعا میخواهید مدت حفظ آمار را تغییر دهید؟ اگر فاصله را کاهش دهید، برخی داده ها حذف میشود", "statistics_cleared": "آمارها با موفقیت حذف شد", + "statistics_enable": "فعالسازی داده های آماری", "interval_hours": "{{count}} ساعت", "interval_hours_plural": "{{count}} ساعت", "filters_configuration": "پیکربندی فیلترها", @@ -440,7 +503,10 @@ "domain_desc": "نامه دامنه یا علامت تطبیقی را برای بازنویسی وارد کنید.", "example_rewrite_domain": "فقط بازنویسی پاسخ برای این دامنه.", "example_rewrite_wildcard": "بازنویسی پاسخ ها برای همه زیردامنه های <0>example.org.", + "disable_ipv6": "غیرفعالسازی IPv6", + "disable_ipv6_desc": "اگر این ویژگی فعال شده، همه جستارهای DNS برای آدرس های IPv6 (نوع AAAA) رها میشود.", "fastest_addr": "سریعترین آدرس آی پی", + "fastest_addr_desc": "جستار همه سرورهای DNS و بازگرداندن سریعترین آدرس IP از میان همه پاسخ ها", "autofix_warning_text": "اگر روی \"تعمیر\" کلیک کنید، AdGuardHome سیستم شما را برای استفاده از DNS سرور AdGuardHome پیکربندی می کند.", "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 از سیستم شما بطور پیش فرض با AdGuardHome پردازش خواهد شد.", @@ -467,6 +533,7 @@ "set_static_ip": "تنظیم یک آدرس آی پی ثابت", "install_static_ok": "خبر خوب! آدرس آی پی ثابت از قبل پیکربندی شده است", "install_static_error": "AdGuard Home نمیتواند رابط این شبکه را خودکار پیکربندی کند. لطفا دستورالعمل چگونگی انجام دستی آن را مطالعه کنید.", + "install_static_configure": "ما تشخیص دادیم از آدرس آی پی پویا استفاده شده است — <0>{{ip}}. آیا میخواهید از آن بعنوان آدرس ثابت استفاده کنید؟", "confirm_static_ip": "AdGuard Home {{ip}} بعنوان آدرس آی پی ثابت شما پیکربندی می کند. ادامه میدهید؟", "list_updated": "{{count}} لیست بروز رسانی شد", "list_updated_plural": "{{count}} لیست بروز رسانی شد", @@ -477,7 +544,7 @@ "show_whitelisted_responses": "لیست سفید", "show_processed_responses": "پردازش شده", "blocked_safebrowsing": "بستن وب گردی اَمن", - "blocked_adult_websites": "مسدود شده با نظارت والدین", + "blocked_adult_websites": "وبسایت غیراخلاقی مسدود شده", "blocked_threats": "تهدیدات مسدود شده", "allowed": "اجازه داده شده", "filtered": "فیلتر شده", @@ -485,8 +552,13 @@ "safe_search": "جستجوی اَمن", "blocklist": "لیست سیاه", "milliseconds_abbreviation": "هـ ثـ", + "cache_optimistic": "حالت ویژه پردازش", + "cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.", "filter_category_general": "General", "filter_category_security": "مسدودسازی بدافزار و فیشینگ", "filter_category_other": "ساير", - "parental_control": "نظارت والدین" + "use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید", + "parental_control": "نظارت والدین", + "safe_browsing": "وب گردی اَمن", + "form_error_password_length": "رمزعبور باید حداقل {{value}} کاراکتر باشد." } diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json index d17d3646..197f74d3 100644 --- a/client/src/__locales/fi.json +++ b/client/src/__locales/fi.json @@ -1,7 +1,7 @@ { "client_settings": "Päätelaiteasetukset", - "example_upstream_reserved": "voit määrittää DNS-ylävirran <0>tietyille verkkotunnuksille", - "example_upstream_comment": "voit määrittää kommentin", + "example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille;", + "example_upstream_comment": "kommentti.", "upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirran palvelimia samanaikaisesti.", "parallel_requests": "Rinnakkaiset pyynnöt", "load_balancing": "Kuormantasaus", @@ -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", @@ -35,7 +35,7 @@ "dhcp_config_saved": "DHCP-asetukset tallennettiin", "dhcp_ipv4_settings": "DHCP:n IPv4-asetukset", "dhcp_ipv6_settings": "DHCP:n IPv6-asetukset", - "form_error_required": "Vaaditaan", + "form_error_required": "Pakollinen kenttä", "form_error_ip4_format": "Virheellinen IPv4-osoite", "form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite", "form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite", @@ -43,11 +43,12 @@ "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 tunniste 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", - "out_of_range_error": "Oltava alueen \"{{start}}\"-\"{{end}}\" ulkopuolella", + "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", "greater_range_end_error": "Oltava alueen päätösarvoa pienempi", @@ -67,10 +68,10 @@ "dhcp_table_hostname": "Isäntänimi", "dhcp_table_expires": "Erääntyy", "dhcp_warning": "Jos tahdot kuitenkin ottaa DHCP-palvelimen käyttöön, varmista, ettei verkossasi ole muita aktiivisia DHCP-palvelimia, koska tämä voi rikkoa Internet-yhteyden muilta verkon laitteilta!", - "dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta.", + "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", @@ -117,7 +118,7 @@ "stats_adult": "Estetyt aikuisille tarkoitetut sivustot", "stats_query_domain": "Kysytyimmät verkkotunnukset", "for_last_24_hours": "viimeisten 24 tunnin ajalta", - "for_last_days": "viimeisen {{count}} päivän ajalta", + "for_last_days": "viimeisten {{count}} päivän ajalta", "for_last_days_plural": "viimeisten {{count}} päivän ajalta", "stats_disabled": "Tilastointi ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksista.", "stats_disabled_short": "Tilastointi ei ole käytössä", @@ -127,7 +128,7 @@ "top_clients": "Käytetyimmät päätelaitteet", "no_clients_found": "Päätelaitteita ei löytynyt", "general_statistics": "Yleiset tilastot", - "number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisen {{count}} päivän ajalta", + "number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta", "number_of_dns_query_days_plural": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta", "number_of_dns_query_24_hours": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten 24 tunnin ajalta", "number_of_dns_query_blocked_24_hours": "Mainoseston suodattimien ja hosts-estolistojen estämien DNS-pyyntöjen määrä", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "AdGuard Home tarkistaa onko verkkotunnus turvallisen selauksen verkkopalvelun estämä. Se käyttää tarkastukseen tietosuojapainotteista rajapintaa: palvelimelle lähetetään vain pieni osa verkkotunnuksen SHA256-hajautusarvosta.", "use_adguard_parental": "Käytä AdGuardin lapsilukko-palvelua", "use_adguard_parental_hint": "AdGuard Home tarkistaa, sisältääkö verkkotunnus aikuisille tarkoitettua sisältöä. Se käyttää samaa tietosuojapainotteista rajapintaa, kuin turvallisen selauksen palvelu.", - "enforce_safe_search": "Pakota turvallinen haku", + "enforce_safe_search": "Käytä turvallista hakua", "enforce_save_search_hint": "AdGuard Home voi pakottaa turvallisen haun käyttöön seuraavissa hakukoneissa: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Palvelimia ei ole määritetty", "general_settings": "Yleiset asetukset", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Syötä yksi sääntö per rivi. Voit käyttää mainoseston sääntöjen tai hosts-tiedostojen syntakseja.", "system_host_files": "Järjestelmän hosts-tiedostot", "examples_title": "Esimerkkejä", - "example_meaning_filter_block": "estä pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin", - "example_meaning_filter_whitelist": "salli pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin", - "example_meaning_host_block": "AdGuard Home palauttaa verkkotunnukselle example.org osoitteen 127.0.0.1 (muttei sen aliverkkotunnuksille)", - "example_comment": "! Tähän tulee kommentti", - "example_comment_meaning": "voit määrittää kommentin", - "example_comment_hash": "# Myös tämä on kommentti", - "example_regex_meaning": "estä pääsy säännöllistä lauseketta vastaaviin verkkotunnuksiin", - "example_upstream_regular": "tavallinen DNS (UDP)", - "example_upstream_dot": "salattu <0>DNS-over-TLS", - "example_upstream_doh": "salattu <0>DNS-over-HTTPS", - "example_upstream_doq": "salattu <0>DNS-over-QUIC", - "example_upstream_sdns": "voit käyttää <0>DNS Stamp -merkintöjä <1>DNSCrypt tai <2>DNS-over-HTTPS -resolvereille", - "example_upstream_tcp": "tavallinen DNS (TCP)", + "example_meaning_filter_block": "estä pääsy verkkotunnukseen example.org sekä kaikkiin sen aliverkkotunnuksiin;", + "example_meaning_filter_whitelist": "salli pääsy verkkotunnukseen example.org sekä kaikkiin sen aliverkkotunnuksiin;", + "example_meaning_host_block": "vastaa verkkotunnukselle example.org IP-osoitteella 127.0.0.1 (muttei sen aliverkkotunnuksille);", + "example_comment": "! Tähän tulee kommentti.", + "example_comment_meaning": "vain kommentti;", + "example_comment_hash": "# Tämäkin on kommentti.", + "example_regex_meaning": "estä pääsy määritettyä säännöllistä lauseketta vastaaviin verkkotunnuksiin.", + "example_upstream_regular": "tavallinen DNS (UDP:n välityksellä);", + "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;", + "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);", "all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla", "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", @@ -259,10 +263,10 @@ "query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä", "query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistoriasi säilytysaikaa? Jos lyhennät aikaa, joitakin tietoja menetetään", "anonymize_client_ip": "Piilota päätelaitteen IP-osoite", - "anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin", + "anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.", "dns_config": "DNS-palvelimen määritys", "dns_cache_config": "DNS-välimuistin määritys", - "dns_cache_config_desc": "Täällä voit määrittää DNS-välimuistin asetukset", + "dns_cache_config_desc": "Tässä voit määrittää DNS-välimuistin.", "blocking_mode": "Estotila", "default": "Oletus", "nxdomain": "NXDOMAIN", @@ -275,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": "Eri päätelaitteet voidaan tunnistaa erityisillä tunnisteilla. Täältä löydät lisätietoja päätelaitteiden tunnistuksesta.", + "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", @@ -309,7 +313,7 @@ "install_settings_listen": "Käytettävä verkkosovitin", "install_settings_port": "Portti", "install_settings_interface_link": "AdGuard Home -asennuksesi hallintapaneeli on käytettävissä seuraavilla osoitteilla:", - "form_error_port": "Syötä oikea portin numero", + "form_error_port": "Syötä kelvollinen portti", "install_settings_dns": "DNS-palvelin", "install_settings_dns_desc": "Sinun on määritettävä laitteesi tai reitittimesi käyttämään DNS-palvelinta seuraavissa osoitteissa:", "install_settings_all_interfaces": "Kaikki verkkosovittimet", @@ -334,41 +338,41 @@ "install_devices_router_list_4": "Joissakin reitittimissä ei ole mahdollista määrittää omaa DNS-palvelinta. Tällöin AdGuard Homen määritys <0>DHCP-palvelimeksi voi auttaa. Muutoin on selvitettävä reitittimen käyttöohjeesta, miten sen DNS-palvelinasetukset muutetaan.", "install_devices_windows_list_1": "Avaa \"Ohjauspaneeli\" Käynnistä-valikon tai Windowsin haun kautta.", "install_devices_windows_list_2": "Avaa \"Verkko ja Internet\" -ryhmä ja sitten \"Verkko ja jakamiskeskus\".", - "install_devices_windows_list_3": "Etsi ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\" ja paina sitä.", - "install_devices_windows_list_4": "Valitse aktiivinen yhteytesi, paina sitä hiiren kakkospainikkeella ja valitse valikosta \"Ominaisuudet\".", - "install_devices_windows_list_5": "Etsi listasta \"Internet protokolla versio 4 (TCP/IP)\", valitse se ja paina jälleen \"Ominaisuudet\".", + "install_devices_windows_list_3": "Paina ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\".", + "install_devices_windows_list_4": "Paina aktiivista yhteyttäsi hiiren kakkospainikkeella ja valitse \"Ominaisuudet\".", + "install_devices_windows_list_5": "Etsi listasta \"Internet Protocol Version 4 (TCP/IPv4)\" (tai IPv6:lle \"Internet Protocol Version 6 (TCP/IPv6)\"), valitse se ja paina jälleen \"Ominaisuudet\".", "install_devices_windows_list_6": "Valitse \"Käytä seuraavia DNS-palvelinten osoitteita\" ja syötä AdGuard Home -palvelimesi osoitteet.", - "install_devices_macos_list_1": "Avaa Omenavalikko ja valitse \"Järjestelmäasetukset \".", + "install_devices_macos_list_1": "Paina Omena-kuvaketta ja valitse \"Järjestelmäasetukset\".", "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", "open_dashboard": "Avaa hallintapaneeli", "install_saved": "Tallenus onnistui", "encryption_title": "Salaus", - "encryption_desc": "Salaus (HTTPS/TLS) DNS-palvelimelle ja selaimen hallintasivulle", + "encryption_desc": "Salaustuki (HTTPS/TLS) DNS:lle ja verkkokäyttölliittymälle.", "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", - "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.", @@ -378,10 +382,10 @@ "encryption_key_input": "Kopioi/liitä tähän varmenteesi PEM-koodattu yksityinen avain.", "encryption_enable": "Käytä salausta (HTTPS, DNS-over-HTTPS ja DNS-over-TLS)", "encryption_enable_desc": "Jos salaus on käytössä, AdGuard Homen hallinta on käytettävissä HTTPS-yhteydellä ja DNS-palvelin kuuntelee pyyntöjä DNS-over-HTTPS ja DNS-over-TLS -yhteyksillä.", - "encryption_chain_valid": "Varmenneketju on pätevä", + "encryption_chain_valid": "Varmenneketju on kelvollinen", "encryption_chain_invalid": "Varmenneketju ei kelpaa", - "encryption_key_valid": "Yksityinen {{type}}-avain on pätevä", - "encryption_key_invalid": "Yksityinen {{type}}-avain ei kelpaa", + "encryption_key_valid": "Tämä yksityinen {{type}}-avain on kelvollinen", + "encryption_key_invalid": "Tämä yksityinen {{type}}-avain ei kelpaa", "encryption_subject": "Aihe", "encryption_issuer": "Toimittaja", "encryption_hostnames": "Isäntänimet", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset.", "topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset.", "form_error_port_range": "Syötä portti väliltä 80-65535", - "form_error_port_unsafe": "Tämä portti ei ole turvallinen", + "form_error_port_unsafe": "Portti ei ole turvallinen", "form_error_equal": "Ei voi olla sama", "form_error_password": "Salasanat eivät täsmää", "reset_settings": "Tyhjennä asetukset", @@ -405,8 +409,8 @@ "update_failed": "Automaattinen päivitys epäonnistui. Seuraa näitä ohjeita päivittääksesi manuaalisesti.", "manual_update": "Seuraa näitä ohjeita päivittääksesi manuaalisesti.", "processing_update": "Odota kun AdGuard Home päivittyy", - "clients_title": "Päätelaitteet", - "clients_desc": "Määritä AdGuard Homeen yhdistetyt päätelaitteet", + "clients_title": "Pysyvät päätelaitteet", + "clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaitetiedot.", "settings_global": "Yleinen", "settings_custom": "Oma", "table_client": "Päätelaite", @@ -417,9 +421,9 @@ "client_edit": "Muokkaa päätelaitetta", "client_identifier": "Tunniste", "ip_address": "IP-osoite", - "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP-osoitteista, CIDR-merkinnöistä, MAC-osoitteista tai erityisistä päätelaitteen ID-tunnisteista (voidaan käyttää DoT/DoH/DoQ kanssa). <0>Täältä voit lukea lisää päätelaitteiden tunnistuksesta.", + "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ä IP-osoite aliverkkoon \"{{cidr}}\"", + "form_enter_subnet_ip": "Syötä aliverkossa \"{{cidr}}\" oleva IP-osoite", "form_enter_mac": "Syötä MAC-osoite", "form_enter_id": "Muokkaa tunnistetta", "form_add_id": "Lisää tunniste", @@ -432,18 +436,18 @@ "clients_not_found": "Päätelaitteita ei löytynyt", "client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?", "list_confirm_delete": "Haluatko varmasti poistaa tämän listan?", - "auto_clients_title": "Päätelaitteet (määrittämättömät)", - "auto_clients_desc": "Tiedot niistä AdGuard Homea käyttävistä päätelaitteista, joita ei ole määritetty asetuksissa", + "auto_clients_title": "Määrittämättömät päätelaitteet", + "auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea.", "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äätelaitteiden ID-tunnisteista. Jos määritetty, 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äätelaitteiden ID-tunnisteista. Jos määritetty, 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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} päivä", "interval_days_plural": "{{count}} päivää", "domain": "Verkkotunnus", + "ecs": "ECS", "punycode": "Punycode", "answer": "Vastaus", "filter_added_successfully": "Lista lisättiin", @@ -507,7 +512,7 @@ "filter_updated": "Listan päivitettiin", "statistics_configuration": "Tilastoinnin määritys", "statistics_retention": "Tilastojen säilytys", - "statistics_retention_desc": "Jos aikaa lyhennetään, joitakin tietoja menetetään.", + "statistics_retention_desc": "Jos aikajaksoa lyhennetään, joitakin tietoja menetetään.", "statistics_clear": "Tyhjennä tilastot", "statistics_clear_confirm": "Haluatko varmasti tyhjentää tilastot?", "statistics_retention_confirm": "Haluatko varmasti muuttaa tilastojen säilytysaikaa? Jos aikaa lyhennetään, joitakin tietoja menetetään.", @@ -532,7 +537,7 @@ "netname": "Verkon nimi", "network": "Verkko", "descr": "Kuvaus", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Lue lisää omien hosts-listojesi luonnista.", "blocked_by_response": "Vastauksen sisältämän CNAME:n tai IP:n estämä", "blocked_by_cname_or_ip": "CNAME:n tai IP:n estämä", @@ -552,10 +557,10 @@ "autofix_warning_list": "Suorittaa toiminnot: <0>Poistaa käytöstä järjestelmän DNSStubListener-palvelun <0>Määrittää DNS-palvelimen osoitteeksi 127.0.0.1 <0>Muuttaa sijainnnin /etc/resolv.conf symbolisen linkin kohteeksi /run/systemd/resolve/resolv.conf <0>Pysäyttää DNSStubListener-palvelun (uudelleenlataa systemd-resolved -palvelu)", "autofix_warning_result": "Tämän jälkeen järjestelmäsi kaikki DNS-pyynnöt käsittelee oletusarvoisesti AdGuard Home.", "tags_title": "Tunnisteet", - "tags_desc": "Voit valita päätelaitteeseen viittaavia tunnisteita. Tunnisteet voidaan sisällyttää suodatussääntöihin ja näin voit kohdistaa niitä tarkemmin. <0>Lue lisää", + "tags_desc": "Voit valita päätelaitetta vastaavia tunnisteita. Tunnisteet voidaan sisällyttää suodatussääntöihin ja näin voit kohdistaa niitä tarkemmin. <0>Lue lisää.", "form_select_tags": "Valitse päätelaitteen tunnisteet", "check_title": "Tarkasta suodatus", - "check_desc": "Tarkasta suodatetaanko isäntänimeä", + "check_desc": "Tarkasta onko isäntänimi suodatettu.", "check": "Tarkasta", "form_enter_host": "Syötä osoite", "filtered_custom_rules": "Suodatettu omilla suodatussäännöillä", @@ -598,15 +603,15 @@ "blocklist": "Estolista", "milliseconds_abbreviation": "ms", "cache_size": "Välimuistin koko", - "cache_size_desc": "DNS-välimuistin koko (tavuina)", + "cache_size_desc": "DNS-välimuistin koko (tavuina).", "cache_ttl_min_override": "Korvaa vähimmäis-TTL", "cache_ttl_max_override": "Korvaa enimmäis-TTL", "enter_cache_size": "Syötä välimuistin koko (tavuina)", "enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)", "enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)", "cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.", - "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden suurin elinaika-arvo (sekunteina)", - "ttl_cache_validation": "Välimuistin elinajan vähimmäisarvon tulee olla pienempi tai sama kuin enimmäisarvon", + "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).", + "ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan", "cache_optimistic": "Optimistinen välimuisti", "cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka sen tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.", "filter_category_general": "Yleiset", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home hylkää tämän päätelaitteen DNS-pyynnöt.", "filter_allowlist": "VAROITUS: Toiminto ohittaa \"{{disallowed_rule}}\" -säännön sallittujen päätelaitteiden listalta.", "last_rule_in_allowlist": "Et voi estää tätä päätelaitetta, koska säännön \"{{disallowed_rule}}\" ohitus POISTAA KÄYTÖSTÄ \"Sallitut päätelaitteet\" -listan.", - "experimental": "Kokeellinen", "use_saved_key": "Käytä aiemmin tallennettua avainta", "parental_control": "Lapsilukko", "safe_browsing": "Turvallinen selaus", - "served_from_cache": "{{value}} (jaettu välimuistista)" + "served_from_cache": "{{value}} (jaettu välimuistista)", + "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä" } diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index e3260cdd..fe891203 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -1,7 +1,7 @@ { "client_settings": "Paramètres du client", - "example_upstream_reserved": "Vous pouvez spécifier un DNS en amont <0>pour un/des domaine(s) spécifique(s)", - "example_upstream_comment": "Vous pouvez spécifier un commentaire", + "example_upstream_reserved": "un amont <0>pour des domaines spécifiques ;", + "example_upstream_comment": " un commentaire.", "upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.", "parallel_requests": "Demandes en parallèle", "load_balancing": "Équilibrage de charge", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Adresse IPv6 invalide", "form_error_ip_format": "Adresse IP invalide", "form_error_mac_format": "Adresse MAC invalide", - "form_error_client_id_format": "L'ID du client ne doit contenir que des chiffres, des lettres minuscules et des traits d'union.", + "form_error_client_id_format": "L'ID du client ne doit contenir que des chiffres, des lettres minuscules et des traits d'union", "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", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "AdGuard Home vérifiera si le domaine est bloqué par le service web de sécurité de la navigation. Il utilisera une API de recherche respectueuse de la vie privée pour effectuer la vérification : seul un préfixe court du hachage SHA256 du nom de domaine est envoyé au serveur.", "use_adguard_parental": "Utiliser le contrôle parental d'AdGuard", "use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.", - "enforce_safe_search": "Utiliser la recherche sécurisée", + "enforce_safe_search": "Utiliser la Recherche Sécurisée", "enforce_save_search_hint": "AdGuard Home appliquera la recherche sécurisée dans les moteurs de recherche suivants : Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Pas de serveurs spécifiés", "general_settings": "Paramètres généraux", @@ -165,10 +166,10 @@ "enabled_filtering_toast": "Filtrage activé", "disabled_safe_browsing_toast": "Navigation sécurisée désactivée", "enabled_safe_browsing_toast": "Navigation sécurisée activée", - "disabled_parental_toast": "Contrôle parental désactivé", - "enabled_parental_toast": "Contrôle parental activé", - "disabled_safe_search_toast": "Recherche sécurisée désactivée", - "enabled_save_search_toast": "Recherche sécurisée activée", + "disabled_parental_toast": "Contrôle Parental désactivé", + "enabled_parental_toast": "Contrôle Parental activé", + "disabled_safe_search_toast": "Recherche Sécurisée désactivée", + "enabled_save_search_toast": "Recherche Sécurisée activée", "enabled_table_header": "Activé", "name_table_header": "Nom", "list_url_table_header": "URL de la liste", @@ -197,28 +198,31 @@ "enter_valid_blocklist": "Saisissez une URL valide vers la liste de blocage.", "enter_valid_allowlist": "Saisissez une URL valide vers la liste d’autorisation.", "form_error_url_format": "Format d’URL incorrect", - "form_error_url_or_path_format": "Entrez une URL ou le chemin absolu de la liste", + "form_error_url_or_path_format": "Lien URL soit chemin absolu de la liste invalide", "custom_filter_rules": "Règles de filtrage d'utilisateur", "custom_filter_rules_hint": "Saisissez la règle en une ligne. C'est possible d'utiliser les règles de blocage ou la syntaxe des fichiers hosts.", "system_host_files": "Fichier d'hôtes système", "examples_title": "Exemples", - "example_meaning_filter_block": "bloque l’accès au domaine example.org et à tous ses sous-domaines", - "example_meaning_filter_whitelist": "débloque l’accès au domaine example.org et à tous ses sous-domaines", - "example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines).", - "example_comment": "! Voici comment ajouter une déscription", - "example_comment_meaning": "commentaire", - "example_comment_hash": "# Et comme ça aussi on peut laisser des commentaires", - "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée", - "example_upstream_regular": "DNS classique (au-dessus de UDP)", - "example_upstream_dot": "<0>DNS-over-TLS chiffré", - "example_upstream_doh": "<0>DNS-over-HTTPS chiffré", - "example_upstream_doq": "<0>DNS-over-QUIC chiffré", - "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps pour <1>DNSCrypt ou les resolveurs <2>DNS_over_HTTPS", - "example_upstream_tcp": "DNS classique (au-dessus de TCP)", + "example_meaning_filter_block": "bloque l’accès au domaine example.org et à tous ses sous-domaines ;", + "example_meaning_filter_whitelist": "débloque l’accès au domaine example.org et à tous ses sous-domaines ;", + "example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines) ;", + "example_comment": "! Voici comment ajouter une déscription.", + "example_comment_meaning": "juste un commentaire ;", + "example_comment_hash": "# Aussi un commentaire.", + "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée .", + "example_upstream_regular": "DNS classique (au-dessus de UDP) ;", + "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é;", + "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) ;", "all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID du client", - "client_id_placeholder": "Saisissez le ID du client", - "client_id_desc": "Les clients différents peuvent être identifiés par aide d'un ID client spécial. Vous trouverez plus d'information sur l'identification des clients ici .", + "client_id": "ClientID", + "client_id_placeholder": "Saisissez le ClientID", + "client_id_desc": "Les clients différents peuvent être identifiés par aide d'un ClientID spécial. Vous trouverez plus d'information sur l'identification des clients ici .", "download_mobileconfig_doh": "Télécharger .mobileconfig pour DNS-sur-HTTPS", "download_mobileconfig_dot": "Télécharger .mobileconfig pour DNS-sur-TLS", "download_mobileconfig": "Télécharger le fichier de configuration", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Entrez la limite de taux", "rate_limit": "Limite de taux", "edns_enable": "Activer le sous-réseau du client EDNS", - "edns_cs_desc": "Envoyer les sous-réseaux des clients aux serveurs DNS.", + "edns_cs_desc": "Ajouter l'option du sous-réseau Client EDNS (ECS) au requêtes en amont et enregistrer les valeurs envoyées par les clients dans le journal des requêtes.", "rate_limit_desc": "Le nombre de requêtes par seconde qu’un seul client est autorisé à faire. Le réglage 0 fait illimité.", "blocking_ipv4_desc": "Adresse IP à renvoyer pour une demande A bloquée", "blocking_ipv6_desc": "Adresse IP à renvoyer pour une demande AAAA bloquée", @@ -309,7 +313,7 @@ "install_settings_listen": "Interface d'écoute", "install_settings_port": "Port", "install_settings_interface_link": "Votre interface web administrateur AdGuard Home sera disponible sur les adresses suivantes :", - "form_error_port": "Entrez un numéro de port valide", + "form_error_port": "Saisissez un numéro de port valide", "install_settings_dns": "Serveur DNS", "install_settings_dns_desc": "Vous devrez configurer vos appareils et votre routeur pour utiliser le serveur DNS sur les adresses suivantes :", "install_settings_all_interfaces": "Toutes les interfaces", @@ -334,8 +338,8 @@ "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": "Sur la partie gauche de l'écran, recherchez « Modifier les paramètres de l'adaptateur » et cliquez dessus.", - "install_devices_windows_list_4": "Sélectionnez votre connexion active, clic droit dessus et sélectionnez Propriétés.", + "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.", "install_devices_macos_list_1": "Cliquez sur l'icône Apple et allez dans les Préférences Système.", @@ -356,11 +360,11 @@ "open_dashboard": "Ouvrir le Tableau de bord", "install_saved": "Enregistré avec succès", "encryption_title": "Chiffrement", - "encryption_desc": "Le support du chiffrement (HTTPS/TLS) pour les DNS et l'interface web administrateur", + "encryption_desc": "Le support du chiffrement (HTTPS/QUIC/TLS) pour les DNS et l'interface web administrateur", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -378,7 +382,7 @@ "encryption_key_input": "Copiez/coller votre clé privée PEM encodée pour votre certificat ici.", "encryption_enable": "Activer le chiffrement (HTTPS, DNS-over-HTTPS et DNS-over-TLS)", "encryption_enable_desc": "Si le chiffrement est activé, l'interface administrateur AdGuard Home fonctionnera via HTTPS et le serveur DNS écoutera les requêtes via DNS-over-HTTPS et DNS-over-TLS.", - "encryption_chain_valid": "Chaîne de certificat valide", + "encryption_chain_valid": "Chaîne de certificat valide.", "encryption_chain_invalid": "Chaîne de certificat invalide", "encryption_key_valid": "Ceci est une clé privée {{type}} valide", "encryption_key_invalid": "Ceci est une clé privée {{type}} invalide", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement.", "topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement.", "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535", - "form_error_port_unsafe": "C'est un port non fiable", + "form_error_port_unsafe": "Port non fiable", "form_error_equal": "Ne doit pas être égal", "form_error_password": "Mots de passe différents", "reset_settings": "Réinitialiser les paramètres", @@ -397,7 +401,7 @@ "setup_guide": "Guide d'installation", "dns_addresses": "Adresses DNS", "dns_start": "Démarrage du serveur DNS", - "dns_status_error": "Erreur lors de la récupération du statut du serveur DNS", + "dns_status_error": "Erreur lors de la vérification du statut du serveur DNS", "down": "Descendant", "fix": "Corriger", "dns_providers": "Voici une <0>liste de fournisseurs DNS connus.", @@ -405,8 +409,8 @@ "update_failed": "Échec de la mise à jour automatique. Veuillez suivre ces étapes pour mettre à jour manuellement.", "manual_update": "Veuillez suivre ces étapes pour mettre à jour manuellement.", "processing_update": "Veuillez patienter, AdGuard Home est en cours de mise à jour", - "clients_title": "Clients", - "clients_desc": "Configurer les appareils connectés à AdGuard Home", + "clients_title": "Clients persistants", + "clients_desc": "Configurer des dossiers de clients persistants pour les appareils connectés à AdGuard Home", "settings_global": "Général", "settings_custom": "Personnalisé", "table_client": "Client", @@ -417,7 +421,7 @@ "client_edit": "Modifier le client", "client_identifier": "Identifiant", "ip_address": "Adresse IP", - "client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP, CIDR, MAC ou un ID client spécial (qui peut être utilisé pour DoT/DoH/DoQ). Vous trouverez plus d'information sur l'identification des clients <0>ici .", + "client_identifier_desc": "Les clients peuvent être identifiés par leur adresse IP, CIDR, adresse MAC ou ClientID (peut être utilisé pour DoT/DoH/DoQ). En savoir plus sur la façon d'identifier les clients <0>ici.", "form_enter_ip": "Saisissez l'IP", "form_enter_subnet_ip": "Saisissez une adresse IP dans le sous-réseau « {{cidr}} »", "form_enter_mac": "Saisissez MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Aucun client trouvé", "client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} »?", "list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?", - "auto_clients_title": "Clients (exécution)", - "auto_clients_desc": "Les données des clients qu'utilisent AdGuard Home, mais non stockées dans la configuration", + "auto_clients_title": "Clients d'exécution", + "auto_clients_desc": "Appareils ne figurant pas sur la liste des clients persistants qui peuvent encore utiliser AdGuard Home.", "access_title": "Paramètres d'accès", - "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home.", + "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home", "access_allowed_title": "Clients autorisés", - "access_allowed_desc": "Une liste de CIDR, d'adresses IP ou d'ID client. S'il est configuré, AdGuard Home acceptera uniquement les demandes de ces clients.", + "access_allowed_desc": "Une liste de CIDRs, d'adresses IP, ou de ClientIDs. Si cette liste comporte des entrées, AdGuard Home n'acceptera que les demandes provenant de ces clients.", "access_disallowed_title": "Clients non autorisés", - "access_disallowed_desc": "Une liste d'adresses IP ou CIDR. Si configuré, AdGuard Home bloquera les requêtes provenant de ces adresses IP. Si des clients sont configurés, ce champ sera ignoré.", + "access_disallowed_desc": "Une liste de CIDRs, d'adresses IP, ou de ClientIDs. Si cette liste comporte des entrées, AdGuard Home abandonnera les demandes provenant de ces clients. Ce champ est ignoré s'il y a des entrées dans Clients autorisés.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} jour", "interval_days_plural": "{{count}} jours", "domain": "Domaine", + "ecs": "ECS", "punycode": "Punycode", "answer": "Réponse", "filter_added_successfully": "Le filtre a été ajouté avec succès", @@ -532,7 +537,7 @@ "netname": "Nom du réseau", "network": "Réseau", "descr": "Description", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Apprenez-en plus à propos de la création de vos propres listes de blocage d’hôtes.", "blocked_by_response": "Bloqué par un CNAME ou une réponse IP", "blocked_by_cname_or_ip": "Bloqué par CNAME ou adresse IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Ceci effectuera les tâches suivantes : <0>Désactiver le système DNSStubListener <0>Définir l’adresse du serveur DNS à 127.0.0.1 <0>Remplacer la cible du lien symbolique de /etc/resolv.conf par /run/systemd/resolve/resolv.conf <0>Arrêter DNSStubListener (recharger le service résolu par systemd)", "autofix_warning_result": "Par conséquent, toutes les demandes DNS de votre système seront traitées par AdGuardHome par défaut.", "tags_title": "Mots clés", - "tags_desc": "Vous pouvez sélectionner les mots clés qui correspondent au client. Les mots clés peuvent être inclus dans les règles de filtrage et vous permettent de les appliquer plus précisément. <0>En savoir plus", + "tags_desc": "Vous pouvez sélectionner les mots clés qui correspondent au client. Les mots clés peuvent être inclus dans les règles de filtrage et vous permettent de les appliquer plus précisément. <0>En savoir plus .", "form_select_tags": "Sélectionner les mots clés du client", "check_title": "Vérification du filtrage", - "check_desc": "Vérifier si le nom d’hôte est filtré", + "check_desc": "Vérifier si le nom d’hôte est filtré .", "check": "Vérifier", "form_enter_host": "Saisissez un nom d’hôte", "filtered_custom_rules": "Filtré par des règles de filtrage personnalisées", @@ -594,18 +599,18 @@ "allowed": "Autorisé", "filtered": "Filtré", "rewritten": "Réécrit", - "safe_search": "Recherche sécurisée", + "safe_search": "Recherche Sécurisée", "blocklist": "Liste de blocage", "milliseconds_abbreviation": "ms", "cache_size": "Taille du cache", - "cache_size_desc": "Taille du cache DNS (en bytes)", + "cache_size_desc": "Taille du cache DNS (en bytes) .", "cache_ttl_min_override": "Remplacer le TTL minimum", "cache_ttl_max_override": "Remplacer le TTL maximum", "enter_cache_size": "Entrer la taille du cache (octets)", "enter_cache_ttl_min_override": "Entrez le TTL minimum (secondes)", "enter_cache_ttl_max_override": "Entrez le TTL maximum (secondes)", - "cache_ttl_min_override_desc": "Prolonger les valeurs courtes de durée de vie (en secondes) reçues du serveur en amont lors de la mise en cache des réponses DNS", - "cache_ttl_max_override_desc": "Établir la valeur de durée de vie TTL maximale (en secondes) pour les saisies dans le cache du DNS", + "cache_ttl_min_override_desc": "Prolonger les valeurs courtes de durée de vie (en secondes) reçues du serveur en amont lors de la mise en cache des réponses DNS .", + "cache_ttl_max_override_desc": "Établir la valeur de durée de vie TTL maximale (en secondes) pour les saisies dans le cache du DNS .", "ttl_cache_validation": "La valeur TTL minimale du cache doit être inférieure ou égale à la valeur maximale", "cache_optimistic": "Caching optimiste", "cache_optimistic_desc": "Faites en sorte qu'AdGuard Home réponde à partir du cache même lorsque les entrées ont expiré et essayez également de les actualiser.", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home ignorera toutes les requêtes DNS de ce client.", "filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.", "last_rule_in_allowlist": "Impossible d’interdire ce client, car l’exclusion de la règle « {{disallowed_rule}} » DÉSACTIVERA la liste des « clients autorisés ».", - "experimental": "Expérimental", "use_saved_key": "Utiliser la clef précédemment enregistrée", "parental_control": "Contrôle parental", "safe_browsing": "Navigation sécurisée", - "served_from_cache": "{{value}} (depuis le cache)" + "served_from_cache": "{{value}} (depuis le cache)", + "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères" } diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index e7642fd2..7b9d5453 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -1,7 +1,7 @@ { "client_settings": "Postavke klijenta", - "example_upstream_reserved": "VI možete odrediti DNS upstream-ove <0>za određene domene", - "example_upstream_comment": "Možete odrediti komentar", + "example_upstream_reserved": "upstream <0>za određene domene;", + "example_upstream_comment": "komentar.", "upstream_parallel": "Koristi paralelne upite kako bi ubrzali rješavanje istovremenim ispitavanjem svih upstream poslužitelja.", "parallel_requests": "Paralelni zahtjevi", "load_balancing": "Load-balancing", @@ -36,17 +36,18 @@ "dhcp_ipv4_settings": "DHCP IPv4 postavke", "dhcp_ipv6_settings": "DHCP IPv6 postavke", "form_error_required": "Obavezno polje", - "form_error_ip4_format": "Nevažeći IPv4 format", + "form_error_ip4_format": "Nevažeća IPv4 adresa", "form_error_ip4_range_start_format": "Nepravilan početak ranga IPv4 adresa", "form_error_ip4_range_end_format": "Nepravilan kraj ranga IPv4 adresa", "form_error_ip4_gateway_format": "Nepravilna IPV4 adresa čvora", - "form_error_ip6_format": "Nevažeći IPv6 format", - "form_error_ip_format": "Nevažeći format IP adrese", - "form_error_mac_format": "Nevažeći MAC format", + "form_error_ip6_format": "Nevažeći IPv6 adresa", + "form_error_ip_format": "Nepravilna IP adresa", + "form_error_mac_format": "Nevažeći MAC adresa", "form_error_client_id_format": "ID klijenta može sadržavati samo brojeve, mala slova i crtice", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Naziv računala", "dhcp_table_expires": "Istječe", "dhcp_warning": "Ako svejedno želite omogućiti DHCP poslužitelj, provjerite da nema drugog aktivnog DHCP poslužitelja na vašoj mreži. Inače može pokvariti Internet za ostale povezane uređaje!", - "dhcp_error": "AdGuard Home nije mogao utvrditi postoji li drugi aktivni DHCP poslužitelj na mreži.", + "dhcp_error": "AdGuard Home nije mogao utvrditi postoji li drugi aktivni DHCP poslužitelj na mreži", "dhcp_static_ip_error": "Da bi se koristio DHCP poslužitelj mora se postaviti statička IP adresa. AdGuard Home nije uspio utvrditi je li ovo mrežno sučelje konfigurirano pomoću statičke IP adrese. Ručno postavite statičku IP adresu.", "dhcp_dynamic_ip_found": "Vaš sustav koristi postavke dinamičke IP adrese za sučelje <0>{{interfaceName}}. Za korištenje DHCP poslužitelja mora se postaviti statička IP adresa. Vaša trenutna IP adresa je <0>{{ipAddress}}. AdGuard Home automatski će postaviti ovu IP adresu kao statičnu ako pritisnete gumb \"Omogući DHCP\".", "dhcp_lease_added": "Statični lease \"{{key}}\" je uspješno dodan", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Unesite jedno pravilo po liniji. Možete koristiti sintaksu za pravila blokiranja oglasa ili za hosts datoteke.", "system_host_files": "Datoteke host sustava", "examples_title": "Primjeri", - "example_meaning_filter_block": "blokira pristup domeni example.org kao i svim njenim poddomenama", - "example_meaning_filter_whitelist": "odblokira pristup domeni example.org kao i svim njenim poddomenama", - "example_meaning_host_block": "AdGuard Home će sada vratiti 127.0.0.1 adresu na example.org domenu (ali ne i poddomene).", - "example_comment": "! Ovdje ide komentar", - "example_comment_meaning": "samo komentar", - "example_comment_hash": "# Također komentar", - "example_regex_meaning": "blokira pristup domenama koje se podudaraju s regularnim izrazom", - "example_upstream_regular": "zadani DNS (putem UDP)", - "example_upstream_dot": "šifrirano <0>DNS-over-TLS", - "example_upstream_doh": "šifrirano <0>DNS-over-HTTPS", - "example_upstream_doq": "šifrirano <0>DNS-over-QUIC", - "example_upstream_sdns": "možete koristiti <0>DNS Stamps za <1>DNSCrypt ili <2>DNS-over-HTTPS rezolvere", - "example_upstream_tcp": "zadani DNS (putem TCP)", + "example_meaning_filter_block": "blokira pristup example.org i svim njenim poddomenama;", + "example_meaning_filter_whitelist": "odblokira pristup example.org i svim njenim poddomenama;", + "example_meaning_host_block": "vratiti 127.0.0.1 adresu na example.org domenu (ali ne i poddomene);", + "example_comment": "! Ovdje ide komentar.", + "example_comment_meaning": "samo komentar;", + "example_comment_hash": "# Također komentar.", + "example_regex_meaning": "blokira pristup domenama koje se podudaraju s regularnim izrazom.", + "example_upstream_regular": "zadani DNS (putem UDP);", + "example_upstream_udp": "obični DNS (preko UDP-a, ime hosta);", + "example_upstream_dot": "šifrirano <0>DNS-over-TLS;", + "example_upstream_doh": "šifrirano <0>DNS-over-HTTPS;", + "example_upstream_doq": "šifrirano <0>DNS-over-QUIC (eksperimentalno);", + "example_upstream_sdns": "<0>DNS Stamps za <1>DNSCrypt ili <2>DNS-over-HTTPS rezolvere;", + "example_upstream_tcp": "zadani DNS (putem TCP);", + "example_upstream_tcp_hostname": "obični DNS (preko TCP-a, ime hosta);", "all_lists_up_to_date_toast": "Svi popisi su ažurirani", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID klijenta", - "client_id_placeholder": "Unesite ID klijenta", - "client_id_desc": "Različiti klijenti mogu se prepoznati pomoću posebnog ID-a klijenta. Ovdje možete saznati više o tome kako prepoznati klijente.", + "client_id": "ClientID", + "client_id_placeholder": "Unesite ClientID", + "client_id_desc": "Različiti klijenti mogu se prepoznati pomoću posebnog ClientID. Ovdje možete saznati više o tome kako prepoznati klijente.", "download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS", "download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS", "download_mobileconfig": "Preuzmite konfiguracijsku datoteku", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Unesite ograničenje", "rate_limit": "Ograničenje", "edns_enable": "Omogući podmrežu klijenta EDNS-a", - "edns_cs_desc": "Pošaljite podmreže klijenata DNS poslužiteljima.", + "edns_cs_desc": "Dodajte opciju EDNS klijentske podmreže (ECS) uzvodnim zahtjevima i zabilježite vrijednosti koje su klijenti poslali u dnevnik upita.", "rate_limit_desc": "Broj zahtjeva u sekundi koji su dopušteni po jednom klijentu. Postavljanje na 0 znači neograničeno.", "blocking_ipv4_desc": "Povratna IP adresa za blokirane A zahtjeve", "blocking_ipv6_desc": "Povratna IP adresa za blokirane AAAA zahtjeve", @@ -334,8 +338,8 @@ "install_devices_router_list_4": "Na nekim se vrstama usmjerivača ne može postaviti prilagođeni DNS poslužitelj. U ovom slučaju, može vam pomoći ako postavite AdGuard Home kao <0>DHCP poslužitelj. U suprotnom, trebali biste potražiti priručnik o tome kako prilagoditi DNS poslužitelje za vaš određeni model routera.", "install_devices_windows_list_1": "Otvorite Upravljačku ploču putem Start izbornika ili Windows pretrage.", "install_devices_windows_list_2": "Idite na kategoriju Mreža i Internet i odaberite Centar za mreže i zajedničko korištenje.", - "install_devices_windows_list_3": "Na lijevoj strani zaslona pronađite \"Promjena postavki prilagodnika\" i kliknite na nju.", - "install_devices_windows_list_4": "Odaberite aktivnu vezu, pritisnite desni klik na nju i odaberite Svojstva.", + "install_devices_windows_list_3": "Na lijevoj strani zaslona kliknite \"Promjena postavki prilagodnika\".", + "install_devices_windows_list_4": "Desnom tipkom miša kliknite svoju aktivnu vezu i odaberite Svojstva.", "install_devices_windows_list_5": "Na popisu pronađite \"Internet Protocol Version 4 (TCP/IPv4)\" (ili, za IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), odaberite ga i zatim ponovno kliknite svojstva.", "install_devices_windows_list_6": "Odaberite \"Koristi sljedeće adrese DNS poslužitelja\" i unesite adrese AdGuard Home poslužitelja.", "install_devices_macos_list_1": "Pritisnite na Apple ikonu i idite u Postavke sustava.", @@ -356,19 +360,19 @@ "open_dashboard": "Otvori upravljačku ploču", "install_saved": "Uspješno spremljeno", "encryption_title": "Šifriranje", - "encryption_desc": "Podrška šifriranja (HTTPS/TLS) za DNS i administratorsko web sučelje", + "encryption_desc": "Podrška šifriranja (HTTPS/QUIC/TLS) za DNS i administratorsko web sučelje", "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", "encryption_https_desc": "Ako je HTTPS port postavljen, AdGuard Home administracijsko sučelje biti će dostupno putem HTTPS-a, a također će pružiti DNS-over-HTTPS na '/dns-query' lokaciji.", "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", - "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": "DNS-over-QUIC port (eksperimentalno)", + "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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja.", "topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja.", "form_error_port_range": "Unesite broj porta od 80 do 65536", - "form_error_port_unsafe": "Ovo je nesigurna port", + "form_error_port_unsafe": "Nesigurna port", "form_error_equal": "Ne bi trebalo biti jednako", "form_error_password": "Lozinka se ne podudara", "reset_settings": "Poništi postavke", @@ -397,15 +401,16 @@ "setup_guide": "Vodič za postavljanje", "dns_addresses": "DNS adrese", "dns_start": "Pokreće se DNS poslužitelj", - "dns_status_error": "Pogreška pri dohvatu statusa DNS poslužitelja", + "dns_status_error": "Pogreška pri provjeravanju statusa DNS poslužitelja", "down": "Ne radi", "fix": "Popravi", "dns_providers": "Ovo je <0>popis poznatih DNS poslužitelja za izbor.", "update_now": "Ažuriraj sada", "update_failed": "Neuspješno automatsko ažuriranje. Molimo pratite ove korake za ručno ažuriranje.", + "manual_update": "Molimo pratite ove korake za ručno ažuriranje.", "processing_update": "Molimo pričekajte, AdGuard Home se ažurira", - "clients_title": "Klijenti", - "clients_desc": "Postavite uređaje povezane na AdGuard Home", + "clients_title": "Uporni klijenti", + "clients_desc": "Konfigurirajte trajne zapise klijenata za uređaje povezane s AdGuard Home", "settings_global": "Globalno", "settings_custom": "Prilagođeno", "table_client": "Klijent", @@ -416,7 +421,7 @@ "client_edit": "Uredi klijenta", "client_identifier": "Identifikator", "ip_address": "IP adresa", - "client_identifier_desc": "Klijenti se mogu identificirati putem IP adrese, CIDR-a, MAC adrese ili posebnog ID-a klijenta (može se koristiti za DoT/DoH/DoQ). <0>Ovdje možete saznati više o tome kako prepoznati klijente.", + "client_identifier_desc": "Klijenti se mogu identificirati putem IP adrese, CIDR-a, MAC adrese ili posebnog ClientID (može se koristiti za DoT/DoH/DoQ). <0>Ovdje možete saznati više o tome kako prepoznati klijente.", "form_enter_ip": "Unesite IP adresu", "form_enter_subnet_ip": "Unesite IP adresu u podmrežu \"{{cidr}}\"", "form_enter_mac": "Unesite MAC adresu", @@ -431,18 +436,18 @@ "clients_not_found": "Nema pronađenih klijenata", "client_confirm_delete": "Jeste li sigurni da želite ukloniti \"{{key}}\" klijenta?", "list_confirm_delete": "Jeste li sigurni da želite ukloniti ovaj popis?", - "auto_clients_title": "Klijenti (runtime)", - "auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne spremaju u postavke", + "auto_clients_title": "Runtime klijenti", + "auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne mijenjaju u postavkama", "access_title": "Postavke pristupa", - "access_desc": "Postavite pravila pristupa za AdGuard Home DNS poslužitelj.", + "access_desc": "Ovdje možete konfigurirati pravila pristupa za AdGuard Home DNS poslužitelj", "access_allowed_title": "Dopušteni klijenti", - "access_allowed_desc": "Popis CIDR-ova, IP adresa ili ID-ova klijenata. Ako je konfiguriran, AdGuard Home prihvatit će zahtjeve samo tih klijenata.", + "access_allowed_desc": "Popis CIDR-ova, IP adresa ili ClientIDs. Ako ovaj popis ima unose, AdGuard Home prihvatit će zahtjeve samo tih klijenata.", "access_disallowed_title": "Nedopušteni klijenti", - "access_disallowed_desc": "Popis CIDR-ova, IP adresa ili ID-ova klijenata. Ako je konfiguriran, AdGuard Home će odbaciti zahtjeve tih klijenata. Ako su konfigurirani dopušteni klijenti, ovo se polje zanemaruje.", + "access_disallowed_desc": "Popis CIDR-ova, IP adresa ili ClientIDs. Ako ovaj popis ima unose, AdGuard Home će odbaciti zahtjeve tih klijenata. Ovo polje se zanemaruje ako postoje unosi u Dopušteni klijenti.", "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", @@ -499,6 +504,7 @@ "interval_days": "{{count}} dan", "interval_days_plural": "{{count}} dana", "domain": "Domena", + "ecs": "ECS", "punycode": "Punycode", "answer": "Odgovor", "filter_added_successfully": "Popis je uspješno dodan", @@ -507,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", @@ -531,7 +537,7 @@ "netname": "Naziv mreže", "network": "Mreža", "descr": "Opis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Saznajte više o stvaranju vlastitog popisa poslužitelja.", "blocked_by_response": "Blokirano od strane CNAME-a ili IP-a u odgovoru", "blocked_by_cname_or_ip": "Blokirao CNAME ili IP", @@ -551,10 +557,10 @@ "autofix_warning_list": "Izvodi sljedeće radnje: <0>Deaktiviraj DNSStubListener sustav <0>Postavi adresu DNS poslužitelja na 127.0.0.1 <0>Zamijeni simbolički cilj veze iz /etc/resolv.conf u /run/systemd/resolve/resolv.conf <0>Zaustavi DNSStubListener (ponovno pokreni systemd-resolved uslugu)", "autofix_warning_result": "Kao rezultat toga, sve DNS zahtjeve iz vašeg sustava će AdGuard Home obraditi prema zadanim postavkama.", "tags_title": "Oznake", - "tags_desc": "Možete odabrati oznake koje odgovaraju klijentu. Oznake se mogu uključiti u pravila filtriranja i omogućuju vam da ih točnije primijenite. <0>Saznajte više", + "tags_desc": "Možete odabrati oznake koje odgovaraju klijentu. Uključite oznake u pravila filtriranja kako biste ih preciznije primijenili. <0>Saznajte više.", "form_select_tags": "Odaberite oznake klijenta", "check_title": "Provjerite filtriranje", - "check_desc": "Provjerite je li naziv računala filtriran", + "check_desc": "Provjerite je li naziv hosta filtriran.", "check": "Provjeri", "form_enter_host": "Unesite naziv računala", "filtered_custom_rules": "Filtrirano prilagođenim pravilima filtriranja", @@ -597,15 +603,15 @@ "blocklist": "Popis nedopuštenih", "milliseconds_abbreviation": "ms", "cache_size": "Veličina predmemorije", - "cache_size_desc": "Veličina DNS predmemorije (u bajtovima)", + "cache_size_desc": "Veličina DNS predmemorije (u bajtovima).", "cache_ttl_min_override": "Nadjačaj minimum TTL-a", "cache_ttl_max_override": "Nadjačaj maksimum TTL-a", "enter_cache_size": "Unesite veličinu predmemorije (u bajtovima)", "enter_cache_ttl_min_override": "Unesite minimalni TTL (u sekundama)", "enter_cache_ttl_max_override": "Unesite maksimalni TTL (u sekundama)", - "cache_ttl_min_override_desc": "Povećajte kratke vrijednosti TTL-a (u sekundama) primljene od upstream poslužitelja prilikom predmemoriranja DNS odgovora", - "cache_ttl_max_override_desc": "Postavite maksimalnu vrijednost TTL-a (u sekundama) za zapise u DNS predmemoriju", - "ttl_cache_validation": "Minimalna vrijednost TTL predmemorije mora biti manja ili jednaka maksimalnoj vrijednosti", + "cache_ttl_min_override_desc": "Povećajte kratke vrijednosti TTL-a (u sekundama) primljene od upstream poslužitelja prilikom predmemoriranja DNS odgovora.", + "cache_ttl_max_override_desc": "Postavite maksimalnu vrijednost TTL-a (u sekundama) za zapise u DNS predmemoriju.", + "ttl_cache_validation": "Minimalno nadjačavanje TTL-a predmemorije mora biti manje ili jednako maksimalnom", "cache_optimistic": "Optimistično predmemoriranje", "cache_optimistic_desc": "Učinite da AdGuard Home reagira iz predmemorije čak i kada su unosi istekli i pokušajte ih osvježiti.", "filter_category_general": "Općenito", @@ -623,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home odbaciti će sve DNS upite od ovog klijenta.", "filter_allowlist": "UPOZORENJE: Ova akcija će također isključiti pravilo \"{{disallowed_rule}}\" s popisa dopuštenih klijenata.", "last_rule_in_allowlist": "Ovaj klijent nije moguće onemogućiti jer će isključivanje pravila \"{{disallowed_rule}}\" ONEMOGUĆITI popis \"Dopušteni klijenti\".", - "experimental": "Eksperimentalno", "use_saved_key": "Korištenje prethodno spremljenog ključa", "parental_control": "Roditeljska zaštita", "safe_browsing": "Sigurno surfanje", - "served_from_cache": "{{value}} (dohvaćeno iz predmemorije)" + "served_from_cache": "{{value}} (dohvaćeno iz predmemorije)", + "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova" } diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json index 6c876315..ffdbf7ce 100644 --- a/client/src/__locales/hu.json +++ b/client/src/__locales/hu.json @@ -1,7 +1,7 @@ { "client_settings": "Kliens beállítások", "example_upstream_reserved": "Megadhat egy DNS kiszolgálót <0>egy adott domainhez vagy domainekhez", - "example_upstream_comment": "Megadhat egy megjegyzést", + "example_upstream_comment": "egy megjegyzés.", "upstream_parallel": "Használjon párhuzamos lekéréseket a domainek feloldásának felgyorsításához az összes upstream kiszolgálóra való egyidejű lekérdezéssel.", "parallel_requests": "Párhuzamos lekérések", "load_balancing": "Terheléselosztás", @@ -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", @@ -39,17 +39,18 @@ "form_error_ip4_format": "Érvénytelen IPv4 cím", "form_error_ip4_range_start_format": "Érvénytelen IPv4-cím a tartomány kezdetéhez", "form_error_ip4_range_end_format": "Érvénytelen IPv4-cím a tartomány végén", - "form_error_ip4_gateway_format": "Érvénytelen IPv4-cím az átjáró", + "form_error_ip4_gateway_format": "Az átjáróhoz (gateway) érvénytelen IPv4 cím lett megadva", "form_error_ip6_format": "Érvénytelen IPv6 cím", "form_error_ip_format": "Érvénytelen IP-cím", "form_error_mac_format": "Érvénytelen MAC cím", - "form_error_client_id_format": "Az ügyfél-azonosító csak számokat, kisbetűket és kötőjeleket tartalmazhat", + "form_error_client_id_format": "A ClientID (kliens azonosító) csak számokat, kisbetűket és kötőjeleket tartalmazhat", "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", - "out_of_range_error": "A tartományon kívül legyen \"{{start}}\"-\"{{end}}\"", + "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": "Nagyobbbb legyen, mint a tartomány kezdete", + "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete", "greater_range_end_error": "Nagyobb legyen, mint a tartomány vége", "subnet_error": "A címeknek egy alhálózatban kell lenniük", "gateway_or_subnet_invalid": "Az alhálózati maszk érvénytelen", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Hosztnév", "dhcp_table_expires": "Lejár", "dhcp_warning": "Ha engedélyezni szeretné a DHCP-kiszolgálót, ellenőrizze, hogy nincs-e más aktív DHCP-kiszolgáló a hálózaton, mert ez megszakíthatja a hálózati eszközök internetkapcsolatát.", - "dhcp_error": "Az AdGuard Home nem tudta megállapítani, hogy van-e másik aktív DHCP-szerver a hálózaton.", + "dhcp_error": "Az AdGuard Home nem tudta megállapítani, hogy van-e másik aktív DHCP-szerver a hálózaton", "dhcp_static_ip_error": "A DHCP szerver használatához statikus IP-címet kell beállítani. Nem sikerült meghatározni, hogy ez a hálózati interfész statikus IP-cím használatával van-e beállítva. Állítson be kézzel egy statikus IP-címet.", "dhcp_dynamic_ip_found": "A rendszer dinamikus IP-cím konfigurációt használ az <0>{{interfaceName}} interfészhez. A DHCP szerver használatához statikus IP-címet kell beállítani. Jelenlegi IP-címe: <0>{{ipAddress}}. Automatikusan beállítjuk ezt az IP címet statikusnak, ha rányom a DHCP engedélyezése gombra.", "dhcp_lease_added": "Statikus bérlet \"{{key}}\" sikeresen hozzáadva", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "Az AdGuard Home ellenőrzi, hogy a böngészési biztonsági modul a domaint tiltólistára tette-e. Az ellenőrzés elvégzéséhez egy adatvédelmet tiszteletben tartó API-t fog használni: a domain név egy rövid előtagját elküldi SHA256 kódolással a szerver felé.", "use_adguard_parental": "Használja az AdGuard szülői felügyelet webszolgáltatását", "use_adguard_parental_hint": "Az AdGuard Home ellenőrzi, hogy a domain tartalmaz-e felnőtteknek szóló anyagokat. Ugyanazokat az adatvédelmi API-kat használja, mint a böngésző biztonsági webszolgáltatás.", - "enforce_safe_search": "Biztonságos keresés kényszerítése", + "enforce_safe_search": "Biztonságos keresés használata", "enforce_save_search_hint": "Az AdGuard Home a következő keresőmotorokban biztosíthatja a biztonságos keresést: Google, Youtube, Bing, DuckDuckGo, Yandex és Pixabay.", "no_servers_specified": "Nincsenek megadott kiszolgálók", "general_settings": "Általános beállítások", @@ -197,28 +198,31 @@ "enter_valid_blocklist": "Adjon meg egy érvényes URL-t a blokkolási listához.", "enter_valid_allowlist": "Adjon meg egy érvényes URL-t az engedélyezési listához.", "form_error_url_format": "Érvénytelen URL formátum", - "form_error_url_or_path_format": "Helytelen URL vagy elérési út a listához", + "form_error_url_or_path_format": "Helytelen URL vagy abszolút elérési útvonal a listához", "custom_filter_rules": "Egyéni szűrési szabályok", "custom_filter_rules_hint": "Adjon meg egy szabályt egy sorban. Használhat egyszerű hirdetésblokkolási szabályokat vagy hosztfájl szintaxist.", "system_host_files": "Rendszer hosztfájlok", "examples_title": "Példák", - "example_meaning_filter_block": "letiltja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is", - "example_meaning_filter_whitelist": "feloldja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is", - "example_meaning_host_block": "Az AdGuard Home mostantol a 127.0.0.1 címre irányítja az example.org domaint (de az aldomaineket nem).", - "example_comment": "! Ide írhat egy megjegyzést", - "example_comment_meaning": "csak egy megjegyzés", - "example_comment_hash": "# Ez is egy megjegyzés", - "example_regex_meaning": "megakadályozza a hozzáférést a reguláris kifejezéssel egyező domaineknél", - "example_upstream_regular": "hagyományos DNS (UDP felett)", - "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", - "example_upstream_sdns": "használhatja a <0> DNS Stamps-ot a <1>DNSCrypt vagy a <2>DNS-over-HTTPS feloldások érdekében", - "example_upstream_tcp": "hagyományos DNS (TCP felett)", + "example_meaning_filter_block": "letiltja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is;", + "example_meaning_filter_whitelist": "feloldja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is;", + "example_meaning_host_block": "az example.org-ot a 127.0.0.1-es címre oldja fel (de az aldomainjeit nem);", + "example_comment": "! Ide írhat egy megjegyzést.", + "example_comment_meaning": "csak egy megjegyzés;", + "example_comment_hash": "# Ez is egy megjegyzés.", + "example_regex_meaning": "blokkolja a hozzáférést azokhoz a domainekhez, amik illeszkednek a megadott reguláris kifejezésre.", + "example_upstream_regular": "hagyományos DNS (UDP felett);", + "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;", + "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);", "all_lists_up_to_date_toast": "Már minden lista naprakész", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "Kliens azonosító", - "client_id_placeholder": "Kliens azonosító megadása", - "client_id_desc": "A különböző klienseket egy speciális kliens azonosító segítségével lehet azonosítani. Itt többet is megtudhat arról, hogyan lehet a klienseket azonosítani.", + "client_id": "Kliens azonosító (ClientID)", + "client_id_placeholder": "Kliens azonosító (ClientID) megadása", + "client_id_desc": "A kliensek a ClientID által kerülnek azonosításra. Tudjon meg többet arról ide kattintva, hogy miként történik a kliensek azonosítása.", "download_mobileconfig_doh": ".mobileconfig letöltése DNS-over-HTTPS-hez", "download_mobileconfig_dot": ".mobileconfig letöltése DNS-over-TLS-hez", "download_mobileconfig": "Konfigurációs fájl letöltése", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Adja meg a kérések maximális számát", "rate_limit": "Kérések korlátozása", "edns_enable": "EDNS kliens alhálózat engedélyezése", - "edns_cs_desc": "Ha engedélyezve van, az AdGuard Home a kliensek alhálózatait küldi el a DNS-kiszolgálóknak.", + "edns_cs_desc": "Adja hozzá az EDNS Client Subnet beállítást (ECS) a felfelé irányuló kérésekhez, és naplózza a kliensek által küldött értékeket a lekérdezési naplóban.", "rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenkén. Ha 0-ra állítja, akkor nincs korlátozás.", "blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím", "blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím", @@ -309,7 +313,7 @@ "install_settings_listen": "Figyelő felület", "install_settings_port": "Port", "install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:", - "form_error_port": "Írja be az érvényes portszámot", + "form_error_port": "Adjon meg egy érvényes portot", "install_settings_dns": "DNS szerver", "install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:", "install_settings_all_interfaces": "Minden felület", @@ -334,8 +338,8 @@ "install_devices_router_list_4": "Bizonyos típusú routereknél nem állíthat be egyéni DNS-kiszolgálót. Ebben az esetben segíthet, ha az AdGuard Home-t DHCP-szerverként állítja be. Ellenkező esetben keresse meg az adott router kézikönyvében a DNS-kiszolgálók testreszabását.", "install_devices_windows_list_1": "Nyissa meg a Vezérlőpultot a Start menün vagy a Windows keresőn keresztül.", "install_devices_windows_list_2": "Válassza a Hálózat és internet kategóriát, majd pedig a Hálózati és megosztási központot.", - "install_devices_windows_list_3": "A képernyő bal oldalán keresse meg az Adapterbeállítások módosítása lehetőséget és kattintson rá.", - "install_devices_windows_list_4": "Válassza ki a jelenleg is használt kapcsolatot, majd jobb egérgombbal kattintson rá és a megjelenő menüből válassza a Tulajdonságok elemet.", + "install_devices_windows_list_3": "A bal oldali panelben kattintson az \"Adapterbeállítások módosítása\" lehetőségre.", + "install_devices_windows_list_4": "Kattintson jobb egérgombbal az aktív kapcsolatra és válassza ki a Tulajdonságokat.", "install_devices_windows_list_5": "Keresse meg az Internet Protocol Version 4 (TCP/IPv4) elemet a listában, válassza ki, majd ismét kattintson a Tulajdonságokra.", "install_devices_windows_list_6": "Válassza a \"Következő DNS címek használata\" lehetőséget és adja meg az AdGuard Home szerver címeit.", "install_devices_macos_list_1": "Kattintson az Apple ikonra és válassza a Rendszerbeállításokat.", @@ -356,19 +360,19 @@ "open_dashboard": "Irányítópult megnyitása", "install_saved": "Sikeres mentés", "encryption_title": "Titkosítás", - "encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára", + "encryption_desc": "Titkosítás (HTTPS/QUIC/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára", "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-TLS 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. 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.", @@ -388,7 +392,7 @@ "encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?", "topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat.", "topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat.", - "form_error_port_range": "A port értékét a 80-65535 tartományban adja meg", + "form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül", "form_error_port_unsafe": "Ez a port nem biztonságos", "form_error_equal": "Nem egyezhetnek", "form_error_password": "A jelszavak nem egyeznek", @@ -403,9 +407,10 @@ "dns_providers": "Itt van az <0>ismert DNS szolgáltatók listája, amelyekből választhat.", "update_now": "Frissítés most", "update_failed": "Az automatikus frissítés nem sikerült. Kérjük, hogy kövesse ezeket a lépéseket a manuális frissítéshez.", + "manual_update": "Kérjük, hogy kövesse ezeket a lépéseket a manuális frissítéshez.", "processing_update": "Kérjük várjon, az AdGuard Home frissítése folyamatban van", - "clients_title": "Kliensek", - "clients_desc": "Az AdGuard Home-hoz csatlakozó eszközök kezelése", + "clients_title": "Fenntartott kliensek", + "clients_desc": "Állítsa be az AdGuard Home-ban fenntartott kliens rekordokat az egyes eszközeihez", "settings_global": "Globális", "settings_custom": "Egyéni", "table_client": "Kliens", @@ -416,7 +421,7 @@ "client_edit": "Kliens módosítása", "client_identifier": "Azonosító", "ip_address": "IP cím", - "client_identifier_desc": "A klienseket az IP-cím, a CIDR, a MAC-cím vagy egy speciális kliens azonosító alapján lehet azonosítani (ez használható DoT/DoH /DoQ esetén). <0>Itt többet is megtudhat a kliensek azonosításáról.", + "client_identifier_desc": "A kliensek azonosíthatók az IP cím, CIDR, MAC cím, vagy a ClientID (ami használható DoT/DoH/DoQ esetén) alapján. Tudjon meg többet arról <0>ide kattintva, hogy miként lehet azonosítani a klienseket.", "form_enter_ip": "IP-cím megadása", "form_enter_subnet_ip": "Adjon meg egy IP címet az alhálózatban \"{{cidr}}\"", "form_enter_mac": "MAC-cím megadása", @@ -431,18 +436,18 @@ "clients_not_found": "Nem található kliens", "client_confirm_delete": "Biztosan törölni szeretné a(z) \"{{key}}\" klienst?", "list_confirm_delete": "Biztosan törölni kívánja ezt a listát?", - "auto_clients_title": "Kliensek (futási idő)", - "auto_clients_desc": "Az AdGuard Home-ot használó, de a konfigurációban nem tárolt kliensek adatai", + "auto_clients_title": "Futási idejű kliensek", + "auto_clients_desc": "Ezek az eszközök nem szerepelnek a fenntartott kliensek listáján, de használják az AdGuard Home-ot", "access_title": "Hozzáférési beállítások", - "access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait.", + "access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait", "access_allowed_title": "Engedélyezett kliensek", - "access_allowed_desc": "CIDR-ek, IP-címek vagy kliensazonosítók listája. Ha be van állítva, akkor az AdGuard Home csak azokat a lekérdezéseket engedélyezi, amelyek ezektől a kliensektől érkeznek.", + "access_allowed_desc": "CIDR-ek, IP-címek vagy ClientID-k listája. Ha be van állítva, akkor az AdGuard Home csak azokat a lekérdezéseket engedélyezi, amelyek ezektől a kliensektől érkeznek.", "access_disallowed_title": "Nem engedélyezett kliensek", - "access_disallowed_desc": "CIDR-ek, IP-címek vagy kliensazonosítók listája. Ha be van állítva, akkor az AdGuard Home eldobja azokat a lekérdezéseket, amelyek ezektől a kliensektől érkeznek. Ha engedélyezett kliensek vannak ide bekonfigurálva, akkor pedig az a mező ki lesz hagyva.", + "access_disallowed_desc": "CIDR-ek, IP-címek vagy ClientID-k listája. Ha be van állítva, akkor az AdGuard Home eldobja azokat a lekérdezéseket, amelyek ezektől a kliensektől érkeznek. Ha engedélyezett kliensek vannak ide bekonfigurálva, akkor pedig az a mező ki lesz hagyva.", "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", @@ -499,6 +504,7 @@ "interval_days": "{{count}} nap", "interval_days_plural": "{{count}} nap", "domain": "Domain", + "ecs": "ECS", "punycode": "Punycode", "answer": "Válasz", "filter_added_successfully": "A lista sikeresen hozzá lett adva", @@ -516,7 +522,7 @@ "interval_hours_plural": "{{count}} óra", "filters_configuration": "Szűrők beállításai", "filters_enable": "Szűrők engedélyezése", - "filters_interval": "Szűrőfrissítési gyakoriság", + "filters_interval": "Szűrők frissítési gyakorisága:", "disabled": "Kikapcsolva", "username_label": "Felhasználónév", "username_placeholder": "Felhasználónév megadása", @@ -531,7 +537,7 @@ "netname": "Hálózat neve", "network": "Hálózat", "descr": "Leírás", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Tudjon meg többet a saját hosztlisták létrehozásáról.", "blocked_by_response": "Blokkolva a CNAME vagy a válasz IP-címe alapján", "blocked_by_cname_or_ip": "CNAME vagy IP által blokkolva", @@ -551,10 +557,10 @@ "autofix_warning_list": "A következő feladatokat hajtja végre: <0>A DNSStubListener rendszer kikapcsolása<0>Beállítja a DNS-kiszolgáló címét 127.0.0.1-re.<0>Lecseréli az /etc/resolv.conf szimbolikus útvonalat erre: /run/systemd/resolve/resolv.conf<0>A DNSStubListener leállítása (a rendszer által feloldott szolgáltatás újratöltése)", "autofix_warning_result": "Mindennek eredményeként az Ön rendszeréből származó összes DNS-kérést alapértelmezés szerint az AdGuard Home dolgozza fel.", "tags_title": "Címkék", - "tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ", + "tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ.", "form_select_tags": "Válasszon kliens címkéket", "check_title": "Szűrés ellenőrzése", - "check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e", + "check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e.", "check": "Ellenőrzés", "form_enter_host": "Adja meg a hosztnevet", "filtered_custom_rules": "Szűrve van az egyéni szűrési szabályok alapján", @@ -597,14 +603,14 @@ "blocklist": "Tiltólista", "milliseconds_abbreviation": "ms", "cache_size": "Gyorsítótár mérete", - "cache_size_desc": "DNS gyorsítótár mérete (bájtokban)", + "cache_size_desc": "DNS gyorsítótár mérete (bájtokban).", "cache_ttl_min_override": "A minimális TTL felülírása", "cache_ttl_max_override": "A maximális TTL felülírása", "enter_cache_size": "Adja meg a gyorsítótár méretét", "enter_cache_ttl_min_override": "Adja meg a minimális TTL-t (másodpercben)", "enter_cache_ttl_max_override": "Adja meg a maximális TTL-t (másodpercben)", - "cache_ttl_min_override_desc": "Megnöveli a DNS kiszolgálótól kapott rövid TTL értékeket (másodpercben), ha gyorsítótárazza a DNS kéréseket", - "cache_ttl_max_override_desc": "Állítson be egy maximális TTL értéket (másodpercben) a DNS gyorsítótár bejegyzéseihez", + "cache_ttl_min_override_desc": "Megnöveli a DNS kiszolgálótól kapott rövid TTL értékeket (másodpercben), ha gyorsítótárazza a DNS kéréseket.", + "cache_ttl_max_override_desc": "Állítson be egy maximális TTL értéket (másodpercben) a DNS gyorsítótár bejegyzéseihez.", "ttl_cache_validation": "A minimális gyorsítótár TTL értéknek kisebbnek vagy egyenlőnek kell lennie a maximum értékkel", "cache_optimistic": "Optimista gyorsítótár", "cache_optimistic_desc": "Lehetővé teszi, hogy az AdGuard Home a gyorsítótárból válaszoljon, még abban az esetben is, ha az ott lévő bejegyzések lejértak, és próbálja meg frissíteni őket.", @@ -623,9 +629,9 @@ "adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.", "filter_allowlist": "FIGYELMEZTETÉS: Ez a művelet a \"{{disallowed_rule}}\" szabályt is kizárja az engedélyezett ügyfelek listájából.", "last_rule_in_allowlist": "Nem lehet letiltani ezt az ügyfelet, mert a \"{{disallowed_rule}}\" szabály kizárása letiltja az \"Allowed clients\" listát.", - "experimental": "Kísérleti", "use_saved_key": "Előzőleg mentett kulcs használata", "parental_control": "Szülői felügyelet", "safe_browsing": "Biztonságos böngészés", - "served_from_cache": "{{value}} (gyorsítótárból kiszolgálva)" + "served_from_cache": "{{value}} (gyorsítótárból kiszolgálva)", + "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen" } diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json index 6449b62d..0b055f31 100644 --- a/client/src/__locales/id.json +++ b/client/src/__locales/id.json @@ -1,7 +1,7 @@ { "client_settings": "Pengaturan klien", - "example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik", - "example_upstream_comment": "Anda dapat menentukan komentar", + "example_upstream_reserved": "upstream <0>untuk domain spesifik;", + "example_upstream_comment": "komentar.", "upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan", "parallel_requests": "Permintaan paralel", "load_balancing": "Penyeimbang beban", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Alamat IPv6 tidak valid", "form_error_ip_format": "Alamat IP tidak valid", "form_error_mac_format": "Alamat MAC tidak valid", - "form_error_client_id_format": "ID Klien hanya boleh berisi angka, huruf kecil, dan tanda hubung", + "form_error_client_id_format": "ClientID hanya boleh berisi angka, huruf kecil, dan tanda hubung", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nama host", "dhcp_table_expires": "Kadaluwarsa", "dhcp_warning": "Jika anda ingin mengaktifkan server DHCP bawaan, pastikan tidak ada server DHCP lain yang aktif. Jika tidak, akan memutus koneksi internet pada perangkat yang telah terhubung!", - "dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan.", + "dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan", "dhcp_static_ip_error": "Jika ingin menggunakan server DHCP, alamat IP statis harus diatur. AdGuard Home gagal menentukan jika antarmuka jaringan ini dikonfigurasi menggunakan alamat IP statis. Silakan atur alamat IP statis secara manual.", "dhcp_dynamic_ip_found": "Sistem Anda menggunakan konfigurasi alamat IP dinamis untuk antarmuka <0>{{interfaceName}}. Untuk menggunakan server DHCP, alamat IP statis harus ditetapkan. Alamat IP Anda saat ini adalah <0>{{ipAddress}}. AdGuard Home akan secara otomatis menetapkan alamat IP ini sebagai statis jika Anda menekan tombol Aktifkan DHCP.", "dhcp_lease_added": "Static lease \"{{key}}\" berhasil ditambahkan", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.", "system_host_files": "File host sistem", "examples_title": "Contoh", - "example_meaning_filter_block": "Blokir akses ke example.org dan seluruh subdomainnya", - "example_meaning_filter_whitelist": "Buka blokir akses ke domain example.orf dan seluruh subdomainnya", - "example_meaning_host_block": "AdGuard Home sekarang akan mengembalikan alamat 127.0.0.1 untuk domain example.org (namun tidak subdomainnya)", - "example_comment": "! Komentar di sini", - "example_comment_meaning": "hanya sebuah komentar", - "example_comment_hash": "Juga sebuah komentar", - "example_regex_meaning": "blokir akses ke domain yang cocok dengan <0>ekspresi reguler yang ditentukan", - "example_upstream_regular": "DNS reguler (melalui UDP)", - "example_upstream_dot": "terenkripsi <0>DNS-over-TLS", - "example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS", - "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC", - "example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS untuk <1>DNSCrypt atau pengarah <2>DNS-over-HTTPS", - "example_upstream_tcp": "DNS reguler (melalui TCP)", + "example_meaning_filter_block": "blokir akses ke example.org dan seluruh subdomainnya;", + "example_meaning_filter_whitelist": "buka blokir akses ke domain example.orf dan seluruh subdomainnya;", + "example_meaning_host_block": "merespons dengan 127.0.0.1 untuk example.org (tetapi tidak untuk subdomainnya);", + "example_comment": "! Komentar di sini.", + "example_comment_meaning": "hanya sebuah komentar;", + "example_comment_hash": "# Juga sebuah komentar.", + "example_regex_meaning": "blokir akses ke domain yang cocok dengan ekspresi reguler yang ditentukan.", + "example_upstream_regular": "DNS reguler (melalui UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Semua daftar sudah diperbarui", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID Klien", - "client_id_placeholder": "Masukkan ID klien", - "client_id_desc": "Klien yang berbeda dapat diidentifikasi dengan ID klien khusus. Di sini Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.", + "client_id": "ClientID", + "client_id_placeholder": "Masukkan ClientID", + "client_id_desc": "Klien dapat diidentifikasi oleh ClientID. Pelajari lebih lanjut tentang cara mengidentifikasi klien di sini.", "download_mobileconfig_doh": "Unduh .mobileconfig untuk DNS-over-HTTPS", "download_mobileconfig_dot": "Unduh .mobileconfig untuk DNS-over-TLS", "download_mobileconfig": "Unduh berkas konfigurasi", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Masukkan batas nilai", "rate_limit": "Batas nilai", "edns_enable": "Aktifkan EDNS Klien Subnet", - "edns_cs_desc": "Kirim subnet klien ke server DNS.", + "edns_cs_desc": "Tambahkan opsi EDNS Client Subnet (ECS) ke permintaan upstream dan catat nilai yang dikirim oleh klien di log kueri.", "rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien. Atur ke 0 untuk tidak terbatas.", "blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir", "blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir", @@ -334,12 +338,12 @@ "install_devices_router_list_4": "Anda tidak dapat menyetel server DNS kustom pada beberapa tipe router. Dalam hal ini mungkin membantu jika Anda mengatur AdGuard Home sebagai <0>server DHCP. Jika tidak, Anda harus mencari petunjuk tentang cara mengkustomisasi server DNS untuk model router khusus Anda.", "install_devices_windows_list_1": "Buka Panel Kontrol melalui menu Start atau pencarian Windows.", "install_devices_windows_list_2": "Masuk ke kategori Jaringan dan Internet (Network and Internet) dan kemudian ke Pusat Jaringan dan Berbagi (Network and Sharing Center).", - "install_devices_windows_list_3": "Di sisi kiri layar temukan \"Ubah pengaturan adaptor\" dan klik di atasnya.", - "install_devices_windows_list_4": "Pilih koneksi aktif Anda, klik kanan padanya dan pilih Properties.", + "install_devices_windows_list_3": "Di panel kiri, klik \"Ubah pengaturan adaptor\".", + "install_devices_windows_list_4": "Klik kanan koneksi aktif Anda dan pilih Properties.", "install_devices_windows_list_5": "Temukan \"Internet Protocol Version 4 (TCP/IPv4)\" (atau, untuk IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\") dalam daftar, pilih dan kemudian klik Properties lagi.", "install_devices_windows_list_6": "Pilih \"Gunakan alamat server DNS berikut\" dan masukkan alamat server Beranda AdGuard Anda.", - "install_devices_macos_list_1": "Klik pada ikon Apple dan pergi ke System Preferences.", - "install_devices_macos_list_2": "Klik pada Jaringan (Network)", + "install_devices_macos_list_1": "Klik ikon Apple dan pergi ke System Preferences.", + "install_devices_macos_list_2": "Klik Network.", "install_devices_macos_list_3": "Pilih koneksi pertama dalam daftar dan klik Advanced.", "install_devices_macos_list_4": "Pilih tab DNS dan masukkan alamat server AdGuard Anda.", "install_devices_android_list_1": "Dari layar beranda Menu Android, ketuk Pengaturan.", @@ -356,19 +360,19 @@ "open_dashboard": "Buka Beranda", "install_saved": "Berhasil disimpan", "encryption_title": "Enkripsi", - "encryption_desc": "Enkripsi (HTTPS / TLS) untuk DNS dan antarmuka admin", + "encryption_desc": "Enkripsi (HTTPS/QUIC/TLS) untuk DNS dan antarmuka admin", "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-lewat-QUIC", - "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.", @@ -403,9 +407,10 @@ "dns_providers": "Berikut adalah <0>daftar penyedia DNS yang dikenal untuk dipilih.", "update_now": "Perbarui sekarang", "update_failed": "Pembaruan otomatis gagal. Silahkan ikuti petunjuk ini untuk perbarui secara manual.", + "manual_update": "Silakan mengikuti langkah berikut untuk memperbarui secara manual.", "processing_update": "Silahkan tunggu, AdGuard Home sedang diperbarui", - "clients_title": "Klien", - "clients_desc": "Atur perangkat yang terhubung ke AdGuard Home", + "clients_title": "Klien yang gigih", + "clients_desc": "Konfigurasikan catatan klien persisten untuk perangkat yang terhubung ke AdGuard Home", "settings_global": "Global", "settings_custom": "Kustom", "table_client": "Klien", @@ -416,7 +421,7 @@ "client_edit": "Ubah Klien", "client_identifier": "Identifikasi", "ip_address": "Alamat IP", - "client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ID klien khusus (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.", + "client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ClientID (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.", "form_enter_ip": "Masukkan IP", "form_enter_subnet_ip": "Masukkan alamat IP di subnet \"{{cidr}}\"", "form_enter_mac": "Masukkan MAC", @@ -432,17 +437,17 @@ "client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?", "list_confirm_delete": "Anda yakin ingin menghapus daftar ini?", "auto_clients_title": "Klien (waktu berjalan)", - "auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi", + "auto_clients_desc": "Perangkat yang tidak ada dalam daftar klien Persisten yang mungkin masih menggunakan AdGuard Home", "access_title": "Pengaturan akses", - "access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS.", + "access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS", "access_allowed_title": "Klien yang diizinkan", - "access_allowed_desc": "Daftar CIDR, alamat IP, atau ID klien. Jika dikonfigurasi, AdGuard Home hanya akan menerima permintaan dari klien ini.", + "access_allowed_desc": "Daftar CIDR, alamat IP, atau ClientID. Jika daftar ini memiliki entri, AdGuard Home hanya akan menerima permintaan dari klien ini.", "access_disallowed_title": "Klien yang tidak diizinkan", - "access_disallowed_desc": "Daftar CIDR, alamat IP, atau ID klien. Jika dikonfigurasi, AdGuard Home akan menghapus permintaan dari klien ini. Jika klien yang diizinkan dikonfigurasi, bidang ini diabaikan.", + "access_disallowed_desc": "Daftar CIDR, alamat IP, atau ClientID. Jika daftar ini memiliki entri, AdGuard Home akan membatalkan permintaan dari klien ini. Bidang ini diabaikan jika ada entri di klien yang diizinkan.", "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", @@ -474,7 +479,7 @@ "dns_rewrites": "DNS rewrite", "form_domain": "Masukkan nama domain", "form_answer": "Masaukan alamat IP atau nama domain", - "form_error_domain_format": "Nama domain tidak valid", + "form_error_domain_format": "Format domain tidak valid", "form_error_answer_format": "Format jawaban tidak valid", "configure": "Konfigurasi", "main_settings": "Pengaturan utama", @@ -499,6 +504,7 @@ "interval_days": "{{count}} hari", "interval_days_plural": "{{count}} hari", "domain": "Domain", + "ecs": "ECS", "punycode": "Kode kecil", "answer": "Jawab", "filter_added_successfully": "Filter telah berhasil ditambahkan", @@ -531,7 +537,7 @@ "netname": "Nama jaringan", "network": "Jaringan", "descr": "Deskripsi", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Pelajari lebih lanjut tentang membuat daftar hitam host Anda sendiri.", "blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon", "blocked_by_cname_or_ip": "Diblokir oleh CNAME atau IP", @@ -551,10 +557,10 @@ "autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener <0> Atur alamat server DNS ke 127.0.0.1 <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)", "autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.", "tags_title": "Tag", - "tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih", + "tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih.", "form_select_tags": "Pilih tag klien", "check_title": "Periksa penyaringan", - "check_desc": "Periksa apakah nama host telah tersaring", + "check_desc": "Periksa apakah nama host telah tersaring.", "check": "Periksa", "form_enter_host": "Masukkan nama host", "filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan", @@ -597,14 +603,14 @@ "blocklist": "Daftar blokir", "milliseconds_abbreviation": "ms", "cache_size": "Ukuran cache", - "cache_size_desc": "Ukuran DNS cache (dalam bit)", + "cache_size_desc": "Ukuran DNS cache (dalam bit).", "cache_ttl_min_override": "Tumpuk TTL minimum", "cache_ttl_max_override": "Tumpuk TTL maksimum", "enter_cache_size": "Masukkan ukuran cache (bytes)", "enter_cache_ttl_min_override": "Masukkan TTL minimum (detik)", "enter_cache_ttl_max_override": "Masukkan TTL maksimum (detik)", - "cache_ttl_min_override_desc": "Perpanjang nilai waktu-online singkat (detik) yang diterima dari server upstream saat menyimpan respons DNS", - "cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS", + "cache_ttl_min_override_desc": "Perpanjang nilai waktu-online singkat (detik) yang diterima dari server upstream saat menyimpan respons DNS.", + "cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS.", "ttl_cache_validation": "Nilai TTL cache minimum harus kurang dari atau sama dengan nilai maksimum", "cache_optimistic": "Caching yang optimis", "cache_optimistic_desc": "Buat AdGuard Home merespons dari cache bahkan ketika entri telah kedaluwarsa dan juga mencoba untuk menyegarkannya.", @@ -623,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.", "filter_allowlist": "PERINGATAN: Tindakan ini juga akan mengecualikan aturan \"{{disallowed_rule}}\" dari daftar klien yang diizinkan.", "last_rule_in_allowlist": "Tidak dapat melarang klien ini karena mengecualikan aturan \"{{disallowed_rule}}\" akan MENONAKTIFKAN daftar \"Klien yang diizinkan\".", - "experimental": "Eksperimental", "use_saved_key": "Gunakan kunci yang disimpan sebelumnya", "parental_control": "Kontrol Orang Tua", "safe_browsing": "Penjelajahan Aman", - "served_from_cache": "{{value}} (disajikan dari cache)" + "served_from_cache": "{{value}} (disajikan dari cache)", + "form_error_password_length": "Kata sandi harus minimal {{value}} karakter" } diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index f0be3db7..9a4ed74b 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -1,7 +1,7 @@ { "client_settings": "Impostazioni client", - "example_upstream_reserved": "Puoi specificare un upstream DNS<0>per lo specifico dominio(i)", - "example_upstream_comment": "Puoi specificare un commento", + "example_upstream_reserved": "un upstream <0>per specifici domini;", + "example_upstream_comment": "un commento.", "upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.", "parallel_requests": "Richieste parallele", "load_balancing": "Bilanciamento del carico", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Indirizzo IPv6 non valido", "form_error_ip_format": "Indirizzo IP non valido", "form_error_mac_format": "Indirizzo MAC non valido", - "form_error_client_id_format": "Il client ID deve contenere solo numeri, lettere minuscole e trattini", + "form_error_client_id_format": "Il ClientID deve contenere solo numeri, lettere minuscole, e trattini", "form_error_server_name": "Nome server non valido", - "form_error_subnet": "La subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"", + "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nome host", "dhcp_table_expires": "Scaduto", "dhcp_warning": "Se desideri attivare il server DHCP integrato, assicurati che non vi siano altri server DHCP attivi, ciò potrebbe causare problemi di connessione alla rete per i dispositivi collegati!", - "dhcp_error": "AdGuard Home non può determinare se è presente un altro server DHCP attivo nella rete.", + "dhcp_error": "AdGuard Home non può determinare se è presente un altro server DHCP attivo nella rete", "dhcp_static_ip_error": "Per utilizzare il server DHCP è necessario impostare un indirizzo IP statico. AdGuard Home non è riuscito a determinare se questa interfaccia di rete è configurata utilizzando un indirizzo IP statico. Ti preghiamo di impostare manualmente un indirizzo IP statico.", "dhcp_dynamic_ip_found": "Il tuo sistema utilizza una configurazione di indirizzi IP dinamici per l'interfaccia <0>{{interfaceName}}. Per poter utilizzare un server DHCP, è necessario impostare un indirizzo IP statico. Il tuo indirizzo IP attuale è <0>{{ipAddress}}. AdGuard Home imposterà automaticamente questo indirizzo come statico quando cliccherai il pulsante \"Attiva server DHCP\".", "dhcp_lease_added": "Lease statici \"{{key}}\" aggiunti correttamente", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "AdGuard Home verificherà se il dominio è bloccato dal servizio web di sicurezza della navigazione. Utilizzerà l'API di ricerca rispettosa della privacy per eseguire il controllo: solo un breve prefisso hash SHA256 del nome di dominio viene inviato al server.", "use_adguard_parental": "Utilizza il Controllo Parentale di AdGuard", "use_adguard_parental_hint": "AdGuard Home verificherà se il dominio contiene materiale per adulti. Utilizza le stesse API privacy-friendly del servizio web 'sicurezza di navigazione'.", - "enforce_safe_search": "Utilizza ricerca sicura", + "enforce_safe_search": "Utilizza Ricerca Sicura", "enforce_save_search_hint": "AdGuard Home forzerà la ricerca sicura sui seguenti motori di ricerca: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Nessun server specificato", "general_settings": "Impostazioni generali", @@ -165,10 +166,10 @@ "enabled_filtering_toast": "Attiva filtri", "disabled_safe_browsing_toast": "Disattiva Navigazione Sicura", "enabled_safe_browsing_toast": "Attiva Navigazione Sicura", - "disabled_parental_toast": "Disattiva il Controllo Parentale", - "enabled_parental_toast": "Attiva Controllo Parentale", - "disabled_safe_search_toast": "Ricerca sicura disattivata", - "enabled_save_search_toast": "Attiva ricerca sicura", + "disabled_parental_toast": "Il Controllo Parentale è disattivato", + "enabled_parental_toast": "Il Controllo Parentale è attivo", + "disabled_safe_search_toast": "La Ricerca Sicura è disattivata", + "enabled_save_search_toast": "La Ricerca Sicura è attiva", "enabled_table_header": "Attivo", "name_table_header": "Nome", "list_url_table_header": "Elenco URL", @@ -196,29 +197,32 @@ "choose_allowlist": "Scegli liste bianche", "enter_valid_blocklist": "Inserisci un URL valido alla lista nera.", "enter_valid_allowlist": "Inserisci un URL valido alla lista bianca.", - "form_error_url_format": "Formato url non valido", + "form_error_url_format": "Formato URL non valido", "form_error_url_or_path_format": "URL o percorso assoluto dell'elenco non validi", "custom_filter_rules": "Regole filtri personalizzate", "custom_filter_rules_hint": "Inserisci una regola per riga. Puoi utilizzare la sintassi delle regole blocca-annunci o quelle dei file hosts.", "system_host_files": "File host di sistema", "examples_title": "Esempi", - "example_meaning_filter_block": "blocca accesso al dominio example.org e a tutti i suoi sottodomini", - "example_meaning_filter_whitelist": "consente l'accesso al dominio esempio.org e a tutti i relativi sottodomini", - "example_meaning_host_block": "AdGuard Home restituirà 127.0.0.1 come indirizzo per il dominio example.org (ma non per i suoi sottodomini)", - "example_comment": "! Qui va un commento", - "example_comment_meaning": "un commento", - "example_comment_hash": "# Un altro commento", - "example_regex_meaning": "blocca l'accesso ai domini che corrispondono alla specifica espressione regolare", - "example_upstream_regular": "DNS regolari (via UDP)", - "example_upstream_dot": "<0>DNS su TLS crittografato", - "example_upstream_doh": "<0>DNS su HTTPS crittografato", - "example_upstream_doq": "<0>DNS su QUIC crittografato", - "example_upstream_sdns": "puoi utilizzare <0>DNS Stamps per <1>DNSCrypt oppure dei risolutori <2>DNS-over-HTTPS", - "example_upstream_tcp": "DNS regolari (via TCP)", + "example_meaning_filter_block": "blocca accesso al dominio example.org e a tutti i suoi sottodomini;", + "example_meaning_filter_whitelist": "consente l'accesso al dominio esempio.org e a tutti i relativi sottodomini;", + "example_meaning_host_block": "restituisce 127.0.0.1 per example.org (ma non per i suoi sottodomini);", + "example_comment": "! Qui va un commento.", + "example_comment_meaning": "solo un commento;", + "example_comment_hash": "# Anche un commento.", + "example_regex_meaning": "blocca l'accesso ai domini corrispondenti alla specifica espressione regolare.", + "example_upstream_regular": "DNS regolare (over UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Tutti gli elenchi sono aggiornati", "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", @@ -259,7 +263,7 @@ "query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa", "query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi", "anonymize_client_ip": "Anonimizza client IP", - "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro e nelle statistiche", + "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche", "dns_config": "Configurazione server DNS", "dns_cache_config": "Configurazione cache DNS", "dns_cache_config_desc": "Qui puoi configurare la cache DNS", @@ -274,10 +278,10 @@ "dnscrypt": "DNSCrypt", "dns_over_https": "DNS su HTTPS", "dns_over_tls": "DNS su TLS", - "dns_over_quic": "DNS su Quic", - "client_id": "ID client", - "client_id_placeholder": "Inserisci ID client", - "client_id_desc": "Client differenti possono essere identificati da uno speciale ID. Qui potrai saperne di più sui metodi per identificarli.", + "dns_over_quic": "DNS su QUIC", + "client_id": "ClientID", + "client_id_placeholder": "Inserisci un ClientID", + "client_id_desc": "I client possono essere identificati attraverso un ClientID. Qui potrai saperne di più sui metodi per identificarli.", "download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS", "download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS", "download_mobileconfig": "Scarica file di configurazione", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Imposta limite delle richieste", "rate_limit": "Limite delle richieste", "edns_enable": "Attiva client di sottorete EDNS", - "edns_cs_desc": "Invia le sottoreti dei client ai server DNS.", + "edns_cs_desc": "Aggiunge l'opzione EDNS Client Subnet (ECS) alle richieste upstream e registra i valori inviati dai client nel registro delle richieste.", "rate_limit_desc": "Il numero di richieste al secondo consentite da un singolo client. Impostare questo valore a 0 rimuove le limitazioni.", "blocking_ipv4_desc": "Indirizzo IP per una richiesta DNS IPv4 bloccata", "blocking_ipv6_desc": "Indirizzo IP restituito per una richiesta DNS IPv6 bloccata", @@ -334,12 +338,12 @@ "install_devices_router_list_4": "Su alcuni tipi di router, non è possibile configurare un server DNS personalizzato. In tal caso, configurare AdGuard Home come un <0>server DHCP potrebbe aiutare. In alternativa, dovresti leggere il manuale di istruzioni per capire come personalizzare i server DNS sul tuo specifico modello di router.", "install_devices_windows_list_1": "Apri il Pannello di controllo tramite il menu Start o la ricerca di Windows.", "install_devices_windows_list_2": "Vai a Rete e categoria Internet e poi a Centro connessioni di rete e condivisione.", - "install_devices_windows_list_3": "Sul lato sinistro dello schermo, trova \"Cambia impostazioni adattatore\" e clicca su di esso.", - "install_devices_windows_list_4": "Seleziona la tua connessione attiva, fai clic destro su di essa e scegli Proprietà.", + "install_devices_windows_list_3": "Sul lato sinistro dello schermo, clicca su \"Cambia impostazioni adattatore\".", + "install_devices_windows_list_4": "Fai clic destro sulla tua connessione attiva e seleziona Proprietà.", "install_devices_windows_list_5": "Trova \"Protocollo Internet versione 4 (TCP/IPv4)\" (o, per IPv6, \"Protocollo Internet versione 6 (TCP/IPv6)\" nell'elenco, selezionalo e quindi clicca nuovamente su Proprietà.", "install_devices_windows_list_6": "Scegli \"Utilizza i seguenti indirizzi server DNS\" ed inserisci i tuoi indirizzi server AdGuard Home.", - "install_devices_macos_list_1": "Fai clic sull'icona Apple e vai su Preferenze di Sistema.", - "install_devices_macos_list_2": "Clicca sulla rete.", + "install_devices_macos_list_1": "Fai clic sull'icona Apple e dirigiti sulle Preferenze di Sistema.", + "install_devices_macos_list_2": "Clicca su Rete.", "install_devices_macos_list_3": "Seleziona la prima connessione nel tuo elenco e clicca su Avanzate.", "install_devices_macos_list_4": "Seleziona la scheda DNS e inserisci gli indirizzi del tuo server AdGuard Home", "install_devices_android_list_1": "Dalla schermata Home Menu di Android, clicca Impostazioni.", @@ -356,19 +360,19 @@ "open_dashboard": "Apri pannello di controllo", "install_saved": "Salvataggio riuscito", "encryption_title": "crittografia", - "encryption_desc": "Supporto di crittografia (HTTPS / TLS) per interfaccia web sia di DNS che di amministrazione", + "encryption_desc": "Supporto alla crittografia (HTTPS/QUIC/TLS) per DNS ed interfaccia web di amministrazione", "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": "DNS su porta QUIC", - "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.", @@ -389,8 +393,8 @@ "topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia .", "topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia .", "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535", - "form_error_port_unsafe": "Questa è una porta non sicura", - "form_error_equal": "Non dovrebbe essere uguale", + "form_error_port_unsafe": "Questa porta non è sicura", + "form_error_equal": "Non deve essere uguale", "form_error_password": "Password non corrispondente", "reset_settings": "Reimposta impostazioni", "update_announcement": "AdGuard Home {{version}} è ora disponibile! <0>Clicca qui per più informazioni.", @@ -405,8 +409,8 @@ "update_failed": "Aggiornamento automatico non riuscito. Ti suggeriamo di seguire questi passaggi per aggiornare manualmente.", "manual_update": "Ti invitiamo a seguire questi passaggi per aggiornare manualmente.", "processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando", - "clients_title": "Client", - "clients_desc": "Configura i dispositivi connessi ad AdGuard Home", + "clients_title": "Client persistenti", + "clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home", "settings_global": "Globale", "settings_custom": "Personalizzato", "table_client": "Client", @@ -417,7 +421,7 @@ "client_edit": "Modifica Client", "client_identifier": "Identificatore", "ip_address": "Indirizzo IP", - "client_identifier_desc": "I client possono essere identificati dall'indirizzo IP, CIDR, indirizzo MAC o un ID speciale (che può essere utilizzato per DoT/DoH/DoQ). <0>Qui potrai saperne di più sui metodi per identificarli.", + "client_identifier_desc": "I client possono essere identificati attraverso il loro indirizzo IP, CIDR, indirizzo MAC o ClientID (che può essere utilizzato per DoT/DoH/DoQ). <0>Qui potrai saperne di più sui metodi per identificarli.", "form_enter_ip": "Inserisci IP", "form_enter_subnet_ip": "Inserisci un indirizzo IP nella subnet \"{{cidr}}\"", "form_enter_mac": "Inserisci MAC", @@ -432,25 +436,25 @@ "clients_not_found": "Nessun client trovato", "client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?", "list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?", - "auto_clients_title": "Clienti (tempo di esecuzione)", - "auto_clients_desc": "Dati dei clienti che utilizzano AdGuard Home, ma che non sono salvati nella configurazione", + "auto_clients_title": "Client in tempo reale", + "auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home", "access_title": "Impostazioni di accesso", - "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home.", + "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home", "access_allowed_title": "Client permessi", - "access_allowed_desc": "Un elenco di CIDR, indirizzi IP o ID client. Se configurata AdGuard Home accetterà richieste solo da questi client.", + "access_allowed_desc": "Un elenco di CIDR, indirizzi IP, o ClientID. Se l'elenco conterrà elementi, AdGuard Home accetterà richieste solo da questi client.", "access_disallowed_title": "Client non permessi", - "access_disallowed_desc": "Un elenco di CIDR, indirizzi IP o ID client. Se configurata, AdGuard Home rifiuterà richieste da questi client. Se i client consentiti risulteranno configurati, questo campo verrà ignorato.", + "access_disallowed_desc": "Un elenco di CIDR, indirizzi IP o ClientID. Se l'elenco conterrà degli elementi, AdGuard Home rifiuterà richieste da questi client. Questo campo verrà ignorato se ci saranno elementi nei client Consentiti.", "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", "setup_dns_privacy_1": "<0>DNS su TLS: Utilizza la stringa <1>{{address}}.", "setup_dns_privacy_2": "<0>DNS su HTTPS: Utilizza la stringa <1>{{address}}.", "setup_dns_privacy_3": "<0>Ecco un elenco di software che è possibile utilizzare.", - "setup_dns_privacy_4": "Si usa un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno file speciale.mobileconfig' che aggiunge i server DNS su HTTPS or DNS su TLS alle configurazioni DNS.", + "setup_dns_privacy_4": "Su un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno speciale file '.mobileconfig' che aggiunge server DNS su HTTPS o DNS su TLS alle configurazioni DNS.", "setup_dns_privacy_android_1": "Android 9 supporta DNS su TLS in modo nativo. Per configurarlo, vai su Impostazioni → Rete e Internet → Avanzate → DNS privato e inserisci qui il tuo nome di dominio.", "setup_dns_privacy_android_2": "<0>AdGuard per Android supporta <1>DNS su HTTPS e <1>DNS su TLS.", "setup_dns_privacy_android_3": "<0>Intra aggiunge <1>DNS su HTTPS il supporto ad Android.", @@ -500,6 +504,7 @@ "interval_days": "{{count}} giorni", "interval_days_plural": "{{count}} giorni", "domain": "Dominio", + "ecs": "ECS", "punycode": "Punycode", "answer": "Risposta", "filter_added_successfully": "L'elenco è stato aggiunto correttamente", @@ -507,7 +512,7 @@ "filter_updated": "L'elenco è stato aggiornato correttamente", "statistics_configuration": "Configurazione delle statistiche", "statistics_retention": "Conservazione delle statistiche", - "statistics_retention_desc": "Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi", + "statistics_retention_desc": "Se dovessi diminuire il valore di intervallo, alcuni dati andranno persi", "statistics_clear": "Azzera statistiche", "statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?", "statistics_retention_confirm": "Sei sicuro di voler modificare la conservazione delle statistiche? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} ore", "filters_configuration": "Configurazione filtri", "filters_enable": "Attiva i filtri", - "filters_interval": "Intervallo aggiornamento filtri", + "filters_interval": "Intervallo aggiornamento filtro", "disabled": "Disattivato", "username_label": "Nome utente", "username_placeholder": "Inserisci nome utente", @@ -532,7 +537,7 @@ "netname": "Nome Network", "network": "Rete", "descr": "Descrizione", - "whois": "Chi è", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Leggi altro su come creare i tuoi elenchi host.", "blocked_by_response": "Bloccato per CNAME o IP in risposta", "blocked_by_cname_or_ip": "Bloccato da CNAME o IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Eseguirà queste attività: <0> Disattiva DNSStubListener di sistema <0> Imposta l'indirizzo del server DNS su 127.0.0.1 <0> Sostituisci la destinazione del collegamento simbolico di /etc/resolv.conf su / run / systemd /resolve/resolv.conf <0> Arresta DNSStubListener (ricarica il servizio systemd-resolved) ", "autofix_warning_result": "Di conseguenza, tutte le richieste DNS dal sistema verranno elaborate da AdGuardHome per impostazione predefinita.", "tags_title": "Tag", - "tags_desc": "È possibile selezionare i tag che corrispondono al client. I tag possono essere inclusi nelle regole dei filtri e consentono di applicarli in modo più accurato. <0> Ulteriori informazioni ", + "tags_desc": "Puoi selezionare i tag che corrispondono al client. È possibile includere tag nelle regole di filtraggio per applicarli in modo più accurato. <0>Per saperne di più.", "form_select_tags": "Seleziona i tag client", "check_title": "Controlla il filtro", - "check_desc": "Controlla se il nome host è filtrato", + "check_desc": "Verifica che il nome host sia filtrato.", "check": "Controlla", "form_enter_host": "Inserisci un nome per l'host", "filtered_custom_rules": "Filtrato dalle regole filtro personalizzate", @@ -594,19 +599,19 @@ "allowed": "Consentito", "filtered": "Filtrato", "rewritten": "Riscritto", - "safe_search": "Ricerca sicura", + "safe_search": "Ricerca Sicura", "blocklist": "Lista nera", "milliseconds_abbreviation": "ms", "cache_size": "Dimensioni cache", - "cache_size_desc": "Dimensioni cache DNS (in byte)", + "cache_size_desc": "Dimensioni cache DNS (in byte).", "cache_ttl_min_override": "Sovrascrivi TTL minimo", "cache_ttl_max_override": "Sovrascrivi TTL massimo", "enter_cache_size": "Immetti dimensioni cache (in byte)", "enter_cache_ttl_min_override": "Immetti TTL minimo (in secondi)", "enter_cache_ttl_max_override": "Immetti TTL massimo (in secondi)", - "cache_ttl_min_override_desc": "Estende i valori brevi (in secondi) ricevuti dal server upstream durante la memorizzazione nella cache delle risposte DNS", - "cache_ttl_max_override_desc": "Imposta un periodo massimo di attivazione (in secondi) per le voci nella cache DNS", - "ttl_cache_validation": "Il valore minimo della cache TTL deve essere inferiore o uguale al valore massimo", + "cache_ttl_min_override_desc": "Estende i valori di breve durata (in secondi) ricevuti dal server upstream durante la memorizzazione nella cache delle risposte DNS.", + "cache_ttl_max_override_desc": "Imposta un valore di durata massima (secondi) per le voci nella cache DNS.", + "ttl_cache_validation": "La sovrascrittura del valore TTL minimo della cache deve essere inferiore o uguale a quello massimo", "cache_optimistic": "Optimistic caching", "cache_optimistic_desc": "Fai in modo che AdGuard Home risponda dalla cache anche quando le voci risultano scadute e prova anche ad aggiornarle.", "filter_category_general": "Generali", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home eliminerà tutte le richieste DNS da questo client.", "filter_allowlist": "ATTENZIONE: Quest'azione escluderà anche la regola \"{{disallowed_rule}}\" dall'elenco di clienti consentiti.", "last_rule_in_allowlist": "Impossibile bloccare questo client perché escludere la regola \"{{disallowed_rule}}\" DISATIVERÁ l'elenco \"Clienti consentiti\".", - "experimental": "Sperimentale", "use_saved_key": "Utilizza la chiave salvata in precedenza", "parental_control": "Controllo Parentale", "safe_browsing": "Navigazione Sicura", - "served_from_cache": "{{value}} (fornito dalla cache)" + "served_from_cache": "{{value}} (fornito dalla cache)", + "form_error_password_length": "La password deve contenere almeno {{value}} caratteri" } diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index bc85b3e6..fed72b6d 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -1,7 +1,7 @@ { "client_settings": "クライアント設定", "example_upstream_reserved": "<0>特定のドメインに対してDNSアップストリームを指定できます。", - "example_upstream_comment": "コメントを指定できます。", + "example_upstream_comment": "コメントを追加できます。", "upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)", "parallel_requests": "並列リクエスト", "load_balancing": "ロードバランシング", @@ -35,7 +35,7 @@ "dhcp_config_saved": "DHCP構成が無事に保存されました。", "dhcp_ipv4_settings": "DHCP IPv4 設定", "dhcp_ipv6_settings": "DHCP IPv6 設定", - "form_error_required": "必須項目", + "form_error_required": "必須項目です", "form_error_ip4_format": "IPv4アドレスが無効です", "form_error_ip4_range_start_format": "範囲開始のIPv4アドレスが無効です", "form_error_ip4_range_end_format": "範囲終了のIPv4アドレスが無効です", @@ -43,15 +43,16 @@ "form_error_ip6_format": "IPv6アドレスが無効です", "form_error_ip_format": "IPアドレスが無効です", "form_error_mac_format": "MACアドレスが無効です", - "form_error_client_id_format": "クライアントIDには、数字、小文字、ハイフンのみが使われている必要があります", - "form_error_server_name": "サーバ名が無効です", - "form_error_subnet": "IPアドレス「{{ip}}」はサブネット「{{cidr}}」に含まれていません", - "form_error_positive": "0より大きい必要があります", - "out_of_range_error": "\"{{start}}\"-\"{{end}}\" の範囲外である必要があります", + "form_error_client_id_format": "ClientIDには、数字、小文字、ハイフン以外は使用できません", + "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": "範囲開始より大きい必要があります", - "greater_range_end_error": "範囲終了より大きい必要があります", - "subnet_error": "アドレスは1つのサブネット内にある必要があります", + "greater_range_start_error": "範囲開始値より大きい値でなければなりません", + "greater_range_end_error": "範囲終了値より大きい値でなければなりません", + "subnet_error": "両アドレスが同じサブネット内にある必要があります", "gateway_or_subnet_invalid": "サブネットマスクが無効です", "dhcp_form_gateway_input": "ゲートウェイIP", "dhcp_form_subnet_input": "サブネットマスク", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "ホスト名", "dhcp_table_expires": "有効期限", "dhcp_warning": "ともかくDHCPサーバを有効にしたい場合は、ネットワーク内で他に稼働中のDHCPサーバがないことを確認してください。そうでなければ、ネットワーク上デバイスでインターネット接続を壊してしまう可能性があります!", - "dhcp_error": "ネットワーク上に別の稼働中DHCPサーバがあるかどうか、AdGuard Homeは判断できませんでした。", + "dhcp_error": "ネットワーク上に別の稼働中DHCPサーバがあるかどうか、AdGuard Homeは判断できませんでした", "dhcp_static_ip_error": "DHCPサーバーを使用するには、静的IPアドレスを設定する必要があります。このネットワークインターフェースが静的IPアドレスを使用するように設定されているかどうかを、AdGuard Homeは判断できませんでした。手動で静的IPアドレスを設定してください。", "dhcp_dynamic_ip_found": "お使いのシステムは、インターフェース<0>{{interfaceName}}用に動的IPアドレス構成を使用しています。DHCPサーバを使用するには、静的IPアドレスで設定する必要があります。あなたの現在のIPアドレスは<0>{{ipAddress}}です。「DHCPサーバを有効にする」ボタンを押すと、AdGuard Homeは自動的にこのIPアドレスを静的IPアドレスとして設定します。", "dhcp_lease_added": "静的割り当て \"{{key}}\" の追加に成功しました", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "AdGuard Homeは、ブラウジング・セキュリティ・ウェブサービスによってドメインがブロックされているかを確認します。 確認は、プライバシーに配慮したルックアップAPIを使用して行います(ドメイン名のSHA256ハッシュの短いプレフィックスのみがサーバーに送信されます)。", "use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する", "use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。", - "enforce_safe_search": "セーフサーチを強制する", + "enforce_safe_search": "セーフサーチを使用する", "enforce_save_search_hint": "AdGuard Homeは、次の検索エンジンでセーフサーチを強制適用します: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay", "no_servers_specified": "サーバが指定されていません", "general_settings": "一般設定", @@ -165,10 +166,10 @@ "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": "セーフサーチを有効にしました", + "disabled_parental_toast": "ペアレンタルコントロールが無効になりました", + "enabled_parental_toast": "ペアレンタルコントロールが有効になりました", + "disabled_safe_search_toast": "セーフサーチが無効になりました", + "enabled_save_search_toast": "セーフサーチが有効になりました", "enabled_table_header": "有効", "name_table_header": "名称", "list_url_table_header": "URLリスト", @@ -196,29 +197,32 @@ "choose_allowlist": "許可リストの選択", "enter_valid_blocklist": "ブロックリストへ有効なURLを入力してください。", "enter_valid_allowlist": "許可リストへ有効なURLを入力してください。", - "form_error_url_format": "URLフォーマットが間違っています", + "form_error_url_format": "URLフォーマットが無効です", "form_error_url_or_path_format": "リストのURLまたは絶対パスが無効です", "custom_filter_rules": "カスタム・フィルタリングルール", "custom_filter_rules_hint": "1つの行に1つのルールを入力してください。 広告ブロックルールやhostsファイル構文を使用できます。", "system_host_files": "システムのhostsファイル", "examples_title": "例", - "example_meaning_filter_block": "example.orgドメインとそのすべてのサブドメインへのアクセスをブロックする", - "example_meaning_filter_whitelist": "example.orgドメインとそのすべてのサブドメインへのアクセスのブロックを解除する", + "example_meaning_filter_block": "example.orgドメインとそのすべてのサブドメインへのアクセスをブロックします。", + "example_meaning_filter_whitelist": "example.orgドメインとそのすべてのサブドメインへのアクセスのブロックを解除します。", "example_meaning_host_block": "AdGuard Homeは、example.orgドメイン(サブドメインを除く)に対して127.0.0.1のアドレスを返すようになります。", - "example_comment": "! ここにはコメントが入ります", - "example_comment_meaning": "ただのコメントです", - "example_comment_hash": "# ここもコメントです", - "example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします", - "example_upstream_regular": "通常の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": "<1>DNSCrypt または <2>DNS-over-HTTPS リゾルバのために <0>DNS Stamps を使えます", - "example_upstream_tcp": "通常のDNS(TCPでの問い合わせ)", + "example_comment": "! コメント本文", + "example_comment_meaning": "コメントが入ります。", + "example_comment_hash": "# これもコメントです", + "example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします。", + "example_upstream_regular": "通常のDNS(over UDP)。", + "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_sdns": "<1>DNSCrypt または <2>DNS-over-HTTPS リゾルバのための <0>DNS Stamps。", + "example_upstream_tcp": "通常のDNS(over TCP)。", + "example_upstream_tcp_hostname": "通常のDNS(over TCP, ホスト名)。", "all_lists_up_to_date_toast": "すべてのリストは既に最新です", "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": "このクライアントを拒否する", @@ -259,10 +263,10 @@ "query_log_strict_search": "完全一致検索には二重引用符を使用します", "query_log_retention_confirm": "クエリ・ログの保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます", "anonymize_client_ip": "クライアントIPを匿名化する", - "anonymize_client_ip_desc": "ログと統計にクライアントの完全なIPアドレスを保存しない", + "anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。", "dns_config": "DNSサーバ設定", "dns_cache_config": "DNSキャッシュ設定", - "dns_cache_config_desc": "ここでDNSキャッシュを設定できます。", + "dns_cache_config_desc": "こちらではDNSキャッシュを設定できます。", "blocking_mode": "ブロックモード", "default": "デフォルト", "nxdomain": "NXDOMAIN", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "Client ID(クライアントID)", - "client_id_placeholder": "クライアントIDを入力してください", - "client_id_desc": "それぞれのクライアントは、特別なクライアントIDで識別できます。 ここでは、クライアントを特定する方法について詳しく知ることができます。", + "client_id": "ClientID(クライアントID)", + "client_id_placeholder": "ClientIDを入力してください", + "client_id_desc": "それぞれのクライアントは、ClinetIDで識別できます。 こちらでは、クライアントを識別する方法について詳しく知ることができます。", "download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード", "download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード", "download_mobileconfig": "設定ファイルをダウンロードする", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "頻度制限を入力してください", "rate_limit": "頻度制限", "edns_enable": "EDNSクライアントサブネットを有効にする", - "edns_cs_desc": "有効にすると、AdGuard HomeはクライアントのサブネットをDNSサーバへ送信します。", + "edns_cs_desc": "アップストリームリクエストにEDNSクライアントサブネットオプション(ECS)を追加し、クライアントから送信された値をクエリログに記録します。", "rate_limit_desc": "一つのクライアントに対して許可される1秒あたりのリクエスト数(「0」に設定すると、制限なしになります)", "blocking_ipv4_desc": "ブロックされたAリクエストに対して応答されるIPアドレス", "blocking_ipv6_desc": "ブロックされたAAAAリクエストに対して応答されるIPアドレス", @@ -334,11 +338,11 @@ "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_3": "左パネルにある「アダプターの設定を変更」をクリックします。", + "install_devices_windows_list_4": "動作中の接続を右クリックし、「プロパティ」を選択します。", "install_devices_windows_list_5": "一覧から「インターネット プロトコル バージョン4(TCP/IPv4)」(もしくはIPv6の場合「インターネット プロトコル バージョン6(TCP/IPv6)」)を見つけ、それを選択してから、もう一度「プロパティ」をクリックします。", "install_devices_windows_list_6": "「次のDNSサーバーアドレスを使う」を選択して、お使いのAdGuard Homeサーバーアドレスを入力します。", - "install_devices_macos_list_1": "Apple アイコンをクリックして「システム環境設定」へ行きます。", + "install_devices_macos_list_1": "Apple アイコンをクリックして「システム環境設定」へ移動します。", "install_devices_macos_list_2": "「ネットワーク」をクリックします。", "install_devices_macos_list_3": "一覧の最初の接続を選択して「詳細...」をクリックします。", "install_devices_macos_list_4": "「DNS」タブを選択して、AdGuard Homeサーバのアドレスを入力します。", @@ -356,11 +360,11 @@ "open_dashboard": "ダッシュボードを開きます", "install_saved": "保存に成功しました", "encryption_title": "暗号化", - "encryption_desc": "DNSと管理ウェブインターフェースの両方に対する暗号化(HTTPS/TLS)をサポートします", + "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 ポート", @@ -368,7 +372,7 @@ "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_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。", "encryption_certificates": "証明書", "encryption_certificates_desc": "暗号化を使用するには、ドメインに有効なSSL証明書チェーンを提供する必要があります。無料の証明書は<0> {{link}} で入手できます。または、信頼できる認証局のいずれかから購入することもできます。", "encryption_certificates_input": "ここにPEM形式の証明書をコピー/ペーストしてください。", @@ -378,26 +382,26 @@ "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_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_port_range": "80〜65535 の範囲内でポート番号を入力してください", + "form_error_port_unsafe": "これは不安全なポートです", "form_error_equal": "同じ値であってはなりません", - "form_error_password": "パスワードが不一致です", + "form_error_password": "パスワードが一致しません", "reset_settings": "設定をリセットする", "update_announcement": "AdGuard Home {{version}}がリリースされました。詳しくは<0>こちらをクリックしてください。", "setup_guide": "セットアップガイド", "dns_addresses": "DNSアドレス", "dns_start": "DNSサーバが起動処理中です", - "dns_status_error": "DNSサーバ・ステータスの取得エラー", + "dns_status_error": "DNSサーバーステータスの確認エラー", "down": "ダウン", "fix": "改善", "dns_providers": "こちらは、選択可能な<0>既知のDNSプロバイダの一覧です。", @@ -405,8 +409,8 @@ "update_failed": "自動更新に失敗しました。手動で更新するには、手順に従ってください。", "manual_update": "手動でアップデートするには、こちらの手順を使ってください。", "processing_update": "AdGuard Homeを更新しています。しばらくお待ちください", - "clients_title": "クライアント", - "clients_desc": "AdGuard Homeに接続されているデバイスを設定します", + "clients_title": "永速的クライアント", + "clients_desc": "AdGuard Homeに接続されているデバイスの永続的クライアント記録を設定できます。", "settings_global": "グローバル", "settings_custom": "カスタム", "table_client": "クライアント", @@ -417,7 +421,7 @@ "client_edit": "クライアントの編集", "client_identifier": "識別子", "ip_address": "IPアドレス", - "client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、または特別なクライアントID(DoT/DoH/DoQで使用可能)によって識別することができます。<0>ここでは、クライアントの識別方法についてより詳しくご確認いただけます。", + "client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、またはClientID(DoT/DoH/DoQに使用可能)によって識別することができます。<0>こちらにて、クライアントの識別方法についてより詳しくご確認いただけます。", "form_enter_ip": "IPアドレスを入力してください", "form_enter_subnet_ip": "サブネット「{{cidr}}」内のIPアドレスを入力してください", "form_enter_mac": "MACアドレスを入力してください", @@ -432,18 +436,18 @@ "clients_not_found": "クライアント情報はありません", "client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?", "list_confirm_delete": "このリストを削除してもよろしいですか?", - "auto_clients_title": "クライアント(実行時)", - "auto_clients_desc": "AdGuard Homeで使用しているが設定に保存されていないクライアント上のデータ", + "auto_clients_title": "ランタイムクライアント", + "auto_clients_desc": "永続的クライアントのリストに未登録で、AdGuard Homeを使用する場合があるデバイスのリスト。", "access_title": "アクセス設定", - "access_desc": "ここで、AdGuard Home DNSサーバのアクセスルールを設定できます。", + "access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。", "access_allowed_title": "許可されたクライアント", - "access_allowed_desc": "CIDR、IPアドレス、またはクライアントIDのリスト。設定されている場合、AdGuard HomeはこれらのIPアドレスからのリクエストのみを受け入れます。", + "access_allowed_desc": "CIDR、IPアドレス、またはClientIDのリスト。このリストに入力がある場合、AdGuard Homeはリストに入っているクライアントからのみリクエストを受け入れます。", "access_disallowed_title": "拒否するクライアント", - "access_disallowed_desc": "CIDR、IPアドレス、またはクライアントIDのリスト。設定されている場合、AdGuard HomeはこれらのIPアドレスからのリクエストを破棄します。「許可されたクライアント」欄が設定されている場合、この欄は無視されます。", + "access_disallowed_desc": "CIDR、IPアドレス、またはClientIDのリスト。リストに入力がある場合、AdGuard Homeはリストに入力されているクライアントからのリクエストを破棄します。※「許可されたクライアント」リストに入力項目がある場合、この「拒否するクライアント」設定は無視されます。", "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プライバシー", @@ -475,8 +479,8 @@ "dns_rewrites": "DNS書き換え", "form_domain": "ドメイン名を入力してください", "form_answer": "IPアドレスかドメイン名を入力", - "form_error_domain_format": "ドメイン名のフォーマットが間違っています", - "form_error_answer_format": "応答フォーマットが間違っています", + "form_error_domain_format": "ドメイン名のフォーマットが無効です", + "form_error_answer_format": "応答のフォーマットが無効です", "configure": "保存", "main_settings": "メイン設定", "block_services": "特定のサービスをブロックする", @@ -500,6 +504,7 @@ "interval_days": "{{count}}日", "interval_days_plural": "{{count}}日", "domain": "ドメイン", + "ecs": "ECS", "punycode": "Punycode", "answer": "応答", "filter_added_successfully": "フィルタの追加に成功しました", @@ -507,7 +512,7 @@ "filter_updated": "フィルタの更新に成功しました", "statistics_configuration": "統計設定", "statistics_retention": "統計保持", - "statistics_retention_desc": "期間を短くすると、一部のデータが失われます", + "statistics_retention_desc": "※保持期間を短くすると、一部のデータは失われます。", "statistics_clear": "統計を消去する", "statistics_clear_confirm": "統計を消去してもよろしいですか?", "statistics_retention_confirm": "統計の保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}}時間", "filters_configuration": "フィルタ設定", "filters_enable": "フィルタを有効にする", - "filters_interval": "フィルタの更新間隔", + "filters_interval": "フィルタの更新頻度", "disabled": "無効", "username_label": "ユーザ名", "username_placeholder": "ユーザ名を入力してください", @@ -532,7 +537,7 @@ "netname": "ネットワーク名", "network": "ネットワーク", "descr": "説明", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "独自ホストリストの作成についての<0>詳細はこちら。", "blocked_by_response": "応答されたCNAMEかIPアドレスによるブロック", "blocked_by_cname_or_ip": "CNAMEもしくはIPアドレスによってブロック済み", @@ -552,10 +557,10 @@ "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>詳細", + "tags_desc": "クライアントに対応するタグを選択できます。フィルタリングルールにタグを含めることで、ルールをより正確に適用できます。 <0>詳細はこちら", "form_select_tags": "クライアントのタグを選択する", "check_title": "フィルタのチェック", - "check_desc": "ホスト名がフィルタで処理されるかをチェックします", + "check_desc": "ホスト名がフィルタリングされているかを確認できます。", "check": "チェックする", "form_enter_host": "ホスト名を入力してください", "filtered_custom_rules": "カスタム・フィルタリングルールによる処理されました", @@ -604,9 +609,9 @@ "enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください", "enter_cache_ttl_min_override": "最小TTL(秒単位)を入力してください", "enter_cache_ttl_max_override": "最大TTL(秒単位)を入力してください", - "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します", - "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します", - "ttl_cache_validation": "最小キャッシュTTL値は最大値以下にする必要があります", + "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します。", + "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します。", + "ttl_cache_validation": "最小キャッシュTTL上書き値は最大値以下にする必要があります", "cache_optimistic": "Optimistic cashing (オプティミスティック・キャッシュ)", "cache_optimistic_desc": "エントリの有効期限が切れた場合でも、AdGuard Homeがキャッシュから応答するようにし、エントリの更新も試みます。", "filter_category_general": "一般", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Homeは、このクライアントからすべてのDNSクエリを落とします。", "filter_allowlist": "【注意】このアクションは、許可されたクライアントのリストから「{{disallowed_rule}}」というルールも除外します。", "last_rule_in_allowlist": "ルール「{{disallowed_rule}}」を除外すると「許可されたクライアント」リストが無効になるため、このクライアントを拒否することはできません。", - "experimental": "実験用", "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/__locales/ko.json b/client/src/__locales/ko.json index ba40f48c..62080721 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -1,7 +1,7 @@ { "client_settings": "클라이언트 설정", - "example_upstream_reserved": "<0>특정 도메인에 대한 DNS 업스트림을 지정할 수 있습니다.", - "example_upstream_comment": "설명을 맞춤 지정할 수 있습니다.", + "example_upstream_reserved": "<0>특정 도메인에 대한 업스트림;", + "example_upstream_comment": "댓글.", "upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.", "parallel_requests": "병렬 처리 요청", "load_balancing": "로드 밸런싱", @@ -35,23 +35,24 @@ "dhcp_config_saved": "DHCP 구성이 성공적으로 저장되었습니다", "dhcp_ipv4_settings": "DHCP IPv4 설정", "dhcp_ipv6_settings": "DHCP IPv6 설정", - "form_error_required": "필수 필드", + "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": "클라이언트 ID는 숫자, 소문자 및 하이픈만 포함해야 합니다", - "form_error_server_name": "유효하지 않은 서버 이름입니다", + "form_error_ip6_format": "잘못된 IPv6 주소", + "form_error_ip_format": "잘못된 IP 주소", + "form_error_mac_format": "잘못된 MAC 주소", + "form_error_client_id_format": "ClientID는 숫자, 소문자 및 붙임표(-)만 포함해야 합니다", + "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": "범위 시작보다 큰 값이어야 합니다", "greater_range_end_error": "범위 종료보다 큰 값이어야 합니다", - "subnet_error": "주소는 하나의 서브넷 아래에 있어야 합니다", + "subnet_error": "주소는 하나의 서브넷에 있어야 합니다", "gateway_or_subnet_invalid": "잘못된 서브넷 마스크", "dhcp_form_gateway_input": "게이트웨이 IP", "dhcp_form_subnet_input": "서브넷 마스크", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "호스트 이름", "dhcp_table_expires": "만료", "dhcp_warning": "DHCP 서버를 사용하려면 네트워크에 다른 활성화된 DHCP 서버가 없는지 확인해 주세요. 다른 활성 DHCP 서버가 있다면, 연결된 장치의 인터넷이 끊길 수 있습니다.", - "dhcp_error": "AdGuard Home이 네트워크에 다른 활성 DHCP 서버가 있는지 확인할 수 없습니다.", + "dhcp_error": "AdGuard Home이 네트워크에 다른 활성 DHCP 서버가 있는지 확인할 수 없습니다", "dhcp_static_ip_error": "DHCP 서버를 사용하려면 고정 IP 주소를 설정해야 합니다. AdGuard Home이 이 네트워크 인터페이스가 고정 IP 주소를 사용하는지 확인할 수 없습니다. 고정 IP 주소를 수동으로 설정하십시오.", "dhcp_dynamic_ip_found": "시스템은 <0>{{interfaceName}} 인터페이스에 동적 IP 주소를 사용합니다. DHCP 서버를 사용하려면 고정 IP 주소를 설정해야 합니다. 현재 IP 주소는 <0>{{ipAddress}}입니다. 'DHCP 서버 활성화' 버튼을 누르면 AdGuard Home이 이 IP 주소를 고정 IP 주소로 자동 설정합니다.", "dhcp_lease_added": "\"{{key}}\" 고정 임대 정상적으로 추가되었습니다", @@ -133,7 +134,7 @@ "number_of_dns_query_blocked_24_hours": "광고 차단 필터 및 호스트 차단 목록에 의해 차단된 DNS 요청 수", "number_of_dns_query_blocked_24_hours_by_sec": "AdGuard 브라우징 보안 모듈에 의해 차단된 DNS 요청 수", "number_of_dns_query_blocked_24_hours_adult": "차단된 성인 웹 사이트의 수", - "enforced_save_search": "세이프 서치 강제", + "enforced_save_search": "세이프서치 강제", "number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수", "average_processing_time": "평균처리 시간", "average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)", @@ -197,28 +198,31 @@ "enter_valid_blocklist": "차단 목록에 유효한 URL을 입력해주세요.", "enter_valid_allowlist": "허용 목록에 유효한 URL을 입력해주세요.", "form_error_url_format": "잘못된 URL 형식", - "form_error_url_or_path_format": "올바른 URL 또는 목록의 절대 경로가 아닙니다", + "form_error_url_or_path_format": "목록의 URL 또는 절대 경로가 잘못되었습니다", "custom_filter_rules": "커스텀 필터링 규칙", "custom_filter_rules_hint": "한 라인에 한 규칙만 입력하세요. 광고 차단 규칙과 호스트 파일 문법 중 하나를 사용할 수 있습니다", "system_host_files": "시스템 호스트 파일", "examples_title": "예시", - "example_meaning_filter_block": "example.org 을 포함한 모든 서브 도메인 접근을 차단합니다", + "example_meaning_filter_block": "example.org 및 모든 하위 도메인에 대한 접근 차단;", "example_meaning_filter_whitelist": "example.org 을 포함한 모든 서브 도메인 접근을 차단 해제합니다.", - "example_meaning_host_block": "AdGuard Home은 example.org 접속 시 127.0.0.1으로 이동합니다. (서브 도메인은 포함되지 않습니다)", - "example_comment": "! 여기는 주석이 올 수 있습니다", - "example_comment_meaning": "말 그대로의 의미입니다", - "example_comment_hash": "# 이것 또한 주석입니다", - "example_regex_meaning": "<0>특정 정규 표현식에 맞는 도메인 접근을 차단합니다", - "example_upstream_regular": "사용자 지정 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": "<1>DNSCrypt나 <2>DNS-over-HTTPS 리졸버를 위해 <0>DNS 스탬프를 사용할 수 있습니다", - "example_upstream_tcp": "사용자 지정 DNS (TCP를 통한 접속)", + "example_meaning_host_block": "example.org에 대해 127.0.0.1로 응답합니다 (하위 도메인은 아님);", + "example_comment": "! 댓글을 추가하는 방법", + "example_comment_meaning": "이것은 단지 댓글입니다;", + "example_comment_hash": "# 이것 또한 댓글입니다.", + "example_regex_meaning": "특정 정규 표현식에 맞는 도메인 접근을 차단합니다.", + "example_upstream_regular": "일반 DNS (UDP을 통한 접속);", + "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": "<1>DNSCrypt 또는 <2>DNS-over-HTTPS 리졸버를 위한 <0>DNS 스탬프;", + "example_upstream_tcp": "일반 DNS (TCP를 통한 접속);", + "example_upstream_tcp_hostname": "일반 DNS (TCP를 통한, 호스트명);", "all_lists_up_to_date_toast": "모든 리스트가 이미 최신입니다", "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": "클라이언트 거부", @@ -259,7 +263,7 @@ "query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요", "query_log_retention_confirm": "정말로 쿼리 로그 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다", "anonymize_client_ip": "클라이언트 IP 익명화", - "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하지 않습니다.", + "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요", "dns_config": "DNS 서버 설정", "dns_cache_config": "DNS 캐시 구성", "dns_cache_config_desc": "여기에서 DNS 캐시를 구성 할 수 있습니다", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "클라이언트 ID", - "client_id_placeholder": "클라이언트 ID 입력", - "client_id_desc": "클라이언트는 특별한 클라이언트 ID를 기반으로 구분됩니다. 여기에서 클라이언트를 구분하는 방법을 자세히 알아보세요.", + "client_id": "ClientID", + "client_id_placeholder": "ClientID 입력", + "client_id_desc": "클라이언트는 ClientID로 식별할 수 있습니다. 여기에서 클라이언트를 식별하는 방법을 자세히 알아보세요.", "download_mobileconfig_doh": "DNS-over-HTTPS용 .mobileconfig 다운로드", "download_mobileconfig_dot": "DNS-over-TLS용 .mobileconfig 다운로드", "download_mobileconfig": "설정 파일 내려받기", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "한도 제한 입력하기", "rate_limit": "한도 제한", "edns_enable": "EDNS 클라이언트 서브넷 활성화", - "edns_cs_desc": "클라이언트의 서브넷을 DNS 서버에 전달합니다.", + "edns_cs_desc": "업스트림 요청에 EDNS 클라이언트 서브넷 옵션(ECS)을 추가하고 쿼리 로그에 클라이언트가 보낸 값을 기록합니다.", "rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)", "blocking_ipv4_desc": "차단된 A 요청에 대해서 반환할 IP 주소", "blocking_ipv6_desc": "차단된 AAAA 요청에 대해서 반환할 IP 주소", @@ -309,7 +313,7 @@ "install_settings_listen": "네트워크 인터페이스", "install_settings_port": "포트", "install_settings_interface_link": "AdGuard Home 관리자 웹 인터페이스는 다음 주소로 제공됨:", - "form_error_port": "유효한 포트 번호를 입력하십시오", + "form_error_port": "유효한 포트 번호를 입력하세요", "install_settings_dns": "DNS 서버", "install_settings_dns_desc": "다음 주소의 DNS 서버를 사용하도록 장치 또는 라우터를 구성해야 합니다.", "install_settings_all_interfaces": "모든 인터페이스", @@ -334,7 +338,7 @@ "install_devices_router_list_4": "일부 라우터 유형에서는 사용자 정의 DNS 서버를 설정할 수 없습니다. 이 경우에는 AdGuard Home을 <0>DHCP 서버로 설정할 수 있습니다. 그렇지 않으면 특정 라우터 모델에 맞게 DNS 서버를 설정하는 방법을 찾아야 합니다.", "install_devices_windows_list_1": "시작 메뉴 또는 윈도우 검색을 통해 제어판을 엽니다.", "install_devices_windows_list_2": "네트워크 및 인터넷 카테고리로 이동한 다음 네트워크 및 공유 센터로 이동합니다.", - "install_devices_windows_list_3": "화면 왼쪽에서 '어댑터 설정 변경'을 찾아 클릭합니다.", + "install_devices_windows_list_3": "화면 왼쪽에서 '어댑터 설정 변경'을 클릭합니다.", "install_devices_windows_list_4": "활성 연결을 선택한 후 우클릭으로 속성을 선택합니다.", "install_devices_windows_list_5": "목록에서 '인터넷 프로토콜 버전 4(TCP/IP)' (또는 IPv6의 경우 '인터넷 프로토콜 버전 6(TCP/IPv6)')를 찾아 선택하고 속성을 클릭합니다.", "install_devices_windows_list_6": "'DNS 서버 주소 사용'을 선택하고 AdGuard Home 서버 주소 입력합니다.", @@ -356,11 +360,11 @@ "open_dashboard": "대시보드 열기", "install_saved": "성공적으로 저장되었습니다", "encryption_title": "암호화", - "encryption_desc": "DNS 및 관리자 웹 인터페이스에 대한 암호화 (HTTPS/TLS) 지원입니다.", + "encryption_desc": "DNS 및 관리 웹 인터페이스에 대한 암호화(HTTPS/TLS)를 지원합니다", "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 포트", @@ -368,7 +372,7 @@ "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_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. ", "encryption_certificates": "인증서", "encryption_certificates_desc": "암호화를 사용하려면 도메인에 대해 올바른 SSL 인증서 체인을 제공해야 합니다. <0>{{link}}에서 무료 증명서를 받을 수도 있고, 신뢰할 수있는 인증 기관에서 구입할 수 있습니다.", "encryption_certificates_input": "PEM으로 인코딩된 인증서 여기에 복사/붙여넣기하세요.", @@ -380,24 +384,24 @@ "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_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_range": "80-65535 범위의 포트 번호를 입력하세요", "form_error_port_unsafe": "안전하지 않은 포트입니다", - "form_error_equal": "동일하지 않아야 함", + "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 서버 상태를 가져오는 도중 오류가 발생했습니다", + "dns_status_error": "DNS 서버 상태를 확인하는 동안 오류가 발생했습니다", "down": "다운로드", "fix": "수정", "dns_providers": "다음은 선택할 수 있는 <0>알려진 DNS 공급자 목록입니다.", @@ -405,8 +409,8 @@ "update_failed": "자동 업데이트 실패 되었습니다. 단계를 따라 수동으로 업데이트하세요", "manual_update": "절차를 따라 수동으로 업데이트하십시오.", "processing_update": "잠시만 기다려주세요, AdGuard Home가 업데이트 중입니다.", - "clients_title": "클라이언트", - "clients_desc": "AdGuard Home에 연결할 기기들을 설정", + "clients_title": "영구 클라이언트", + "clients_desc": "AdGuard Home에 연결된 기기에 대한 영구 클라이언트 레코드를 설정합니다", "settings_global": "글로벌", "settings_custom": "사용자", "table_client": "클라이언트", @@ -417,7 +421,7 @@ "client_edit": "클라이언트 수정", "client_identifier": "식별자", "ip_address": "IP 주소", - "client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 특수 클라이언트 ID로 식별할 수 있습니다 (DoT/DoH/DoQ에 사용 가능). <0>여기에서 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.", + "client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 ClientID(DoT/DoH/DoQ에 사용 가능)로 식별할 수 있습니다. <0>여기에서 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.", "form_enter_ip": "IP 입력", "form_enter_subnet_ip": "서브넷 \"{{cidr}}\" 내의 IP 주소 입력", "form_enter_mac": "MAC 입력", @@ -432,18 +436,18 @@ "clients_not_found": "클라이언트 없음", "client_confirm_delete": "정말 클라이언트 \"{{key}}\" 삭제하시겠습니까?", "list_confirm_delete": "정말로 이 목록을 제거하시겠습니까?", - "auto_clients_title": "클라이언트 (런타임)", - "auto_clients_desc": "AdGuard Home을 사용하지만 구성에 저장되지 않은 클라이언트의 데이터입니다.", + "auto_clients_title": "런타임 클라이언트", + "auto_clients_desc": "AdGuard Home을 계속 사용할 수 있는 영구 클라이언트 목록에 없는 디바이스입니다", "access_title": "접근 설정", - "access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 구성할 수 있습니다.", + "access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 설정할 수 있습니다", "access_allowed_title": "허용된 클라이언트", - "access_allowed_desc": "CIDR, IP 주소 또는 클라이언트 ID 목록입니다. 허용된 클라이언트가 구성된 경우, AdGuard Home은 이 클라이언트의 요청만 수락합니다.", + "access_allowed_desc": "CIDR, IP 주소 또는 ClientID 목록입니다. 이 목록에 항목이 있는 경우, AdGuard Home은 이러한 클라이언트의 요청만 수락합니다.", "access_disallowed_title": "차단된 클라이언트", - "access_disallowed_desc": "CIDR, IP 주소 또는 클라이언트 ID 목록입니다. 차단된 클라이언트가 구성된 경우, AdGuard Home은 이 클라이언트의 요청을 무시합니다. 허용된 클라이언트가 구성된 경우, 이 필드는 무시됩니다.", + "access_disallowed_desc": "CIDR, IP 주소 또는 ClientID 목록입니다. 이 목록에 항목이 있는 경우, AdGuard Home은 이러한 클라이언트의 요청을 무시합니다. 허용된 클라이언트에 항목이 있는 경우, 이 필드는 무시됩니다.", "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 프라이버시", @@ -476,7 +480,7 @@ "form_domain": "도메인 이름 또는 와일드카드를 입력합니다", "form_answer": "IP 주소 또는 도메인 이름을 입력하세요", "form_error_domain_format": "도메인 형식이 잘못되었습니다", - "form_error_answer_format": "답변 형식이 잘못되었습니다. ", + "form_error_answer_format": "답변 형식이 잘못되었습니다", "configure": "설정하기", "main_settings": "기본 설정", "block_services": "특정 서비스 차단", @@ -500,6 +504,7 @@ "interval_days": "{{count}} 일", "interval_days_plural": "{{count}} 일", "domain": "도메인", + "ecs": "ECS", "punycode": "Punycode", "answer": "응답", "filter_added_successfully": "목록이 성공적으로 추가됨", @@ -507,7 +512,7 @@ "filter_updated": "필터가 성공적으로 업데이트됨", "statistics_configuration": "통계 구성", "statistics_retention": "통계 저장 기간", - "statistics_retention_desc": "값을 줄이면 설정한 값보다 오래된 데이터가 소멸됩니다.", + "statistics_retention_desc": "주기를 줄이면, 일부 데이터가 손실됩니다", "statistics_clear": "통계 초기화", "statistics_clear_confirm": "통계를 정말로 초기화하시겠습니까?", "statistics_retention_confirm": "정말로 통계 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다", @@ -532,7 +537,7 @@ "netname": "네트워크 이름", "network": "네트워크", "descr": "설명", - "whois": "후이즈", + "whois": "WHOIS", "filtering_rules_learn_more": "차단 리스트를 직접 호스트하는 법을 <0>알아보세요.", "blocked_by_response": "응답 중 차단된 CNAME 또는 IP", "blocked_by_cname_or_ip": "CNAME 또는 IP에 의해 차단됨", @@ -552,10 +557,10 @@ "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>자세히 알아보기", + "tags_desc": "클라이언트에 해당하는 태그를 선택할 수 있습니다. 필터링 규칙에 태그를 포함시키면 더 정확하게 적용시킬 수 있습니다. <0>자세히 알아보기.", "form_select_tags": "클라이언트 태그 선택", "check_title": "필터링 확인", - "check_desc": "호스트 이름이 필터링되는지 확인", + "check_desc": "호스트 이름이 필터링되는지 확인합니다.", "check": "확인", "form_enter_host": "호스트 이름을 입력해주세요", "filtered_custom_rules": "사용자 정의 필터링 규칙으로 필터링됨", @@ -598,14 +603,14 @@ "blocklist": "차단 목록", "milliseconds_abbreviation": "ms", "cache_size": "캐시 크기", - "cache_size_desc": "DNS 캐시 크기 (바이트)", + "cache_size_desc": "DNS 캐시 크기 (바이트).", "cache_ttl_min_override": "최소 TTL (초) 무시", "cache_ttl_max_override": "최대 TTL (초) 무시", "enter_cache_size": "캐시 크기를 입력하세요", "enter_cache_ttl_min_override": "최소 TTL을 입력하세요", "enter_cache_ttl_max_override": "최대 TTL을 입력하세요", - "cache_ttl_min_override_desc": "업스트림 서버에서 수신한 TTL 값(최소)을 무시합니다", - "cache_ttl_max_override_desc": "업스트림 서버에서 수신한 TTL 값(최대)을 무시합니다", + "cache_ttl_min_override_desc": "DNS 응답을 캐싱할 때 업스트림 서버에서 수신한 짧은 TTL 값(초)을 확장합니다.", + "cache_ttl_max_override_desc": "DNS 캐시의 항목에 대한 최대 TTL 값(초)을 설정합니다.", "ttl_cache_validation": "최소 캐시 TTL 값은 최대 값보다 이하여야 합니다", "cache_optimistic": "옵티미스틱 캐시", "cache_optimistic_desc": "세션이 만료되었거나 새로고침을 시도하는 경우에도 AdGuard Home이 캐시를 기반으로 응답하도록 합니다.", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home은 이 클라이언트에서 모든 DNS 쿼리를 삭제합니다.", "filter_allowlist": "경고: 이 경우 허용된 클라이언트 목록에서 '{{disallowed_rule}}' 규칙 또한 제외됩니다.", "last_rule_in_allowlist": "'{{disallowed_rule}}' 규칙을 제외하면 '허용된 클라이언트' 목록이 꺼지므로 해당 클라이언트를 제외할 수 없습니다.", - "experimental": "실험", "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/__locales/nl.json b/client/src/__locales/nl.json index b6933045..408dc561 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -1,7 +1,7 @@ { "client_settings": "Cliëntinstellingen", - "example_upstream_reserved": "Je kan een DNS-upstream opgeven <0>voor specifieke domein(en)", - "example_upstream_comment": "Je kan je commentaar specifiëren", + "example_upstream_reserved": "een upstream <0>voor specifieke domeinen;", + "example_upstream_comment": "een commentaar.", "upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.", "parallel_requests": "Parallelle verzoeken", "load_balancing": "Volume balanceren", @@ -9,7 +9,7 @@ "bootstrap_dns": "Bootstrap DNS-servers", "bootstrap_dns_desc": "Bootstrap DNS-servers worden gebruikt om IP-adressen op te lossen van de DoH / DoT-resolvers die u opgeeft als upstreams.", "local_ptr_title": "Private omgekeerde DNS-servers", - "local_ptr_desc": "De DNS-servers die AdGuard Home zal gebruiken voor lokale PTR zoekopdrachten. Deze server wordt gebruikt om de hostnamen van de clients met private IP-adressen, bijvoorbeeld \"192.168.12.34\", dmv. rDNS. Indien niet ingesteld, gebruikt AdGuard Home automatisch je standaard DNS-resolver.", + "local_ptr_desc": "De DNS-servers die AdGuard Home gebruikt voor lokale PTR-zoekopdrachten. Deze servers worden gebruikt om PTR-verzoeken voor adressen in privé-IP-bereiken op te lossen, bijvoorbeeld \"192.168.12.34\", met behulp van reverse DNS. Indien niet ingesteld, gebruikt AdGuard Home de adressen van de standaard DNS-resolvers van uw besturingssysteem, behalve de adressen van AdGuard Home zelf.", "local_ptr_default_resolver": "Standaard gebruikt AdGuard Home de volgende omgekeerde DNS-resolvers: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home kon voor dit systeem geen geschikte private omgekeerde DNS-resolvers bepalen.", "local_ptr_placeholder": "Voer één serveradres per regel in", @@ -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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Host naam", "dhcp_table_expires": "Verloopt op", "dhcp_warning": "Indien je de ingebouwde DHCP server wilt inschakelen, let dan op dat er geen andere actieve DHCP server aanwezig is in je netwerk. Dit kan de internetverbinding instabiel maken voor sommige apparaten in je netwerk!", - "dhcp_error": "AdGuard Home kon niet bepalen of er een andere actieve DHCP server in je netwerk aanwezig is.", + "dhcp_error": "AdGuard Home kon niet bepalen of er een andere actieve DHCP server op het netwerk aanwezig is", "dhcp_static_ip_error": "Om de DHCP server te gebruiken, moet een statisch IP-adres worden ingesteld. AdGuard Home heeft niet kunnen vaststellen of de netwerkinterface is geconfigureerd met een statisch IP-adres. Stel handmatig een statisch IP-adres in.", "dhcp_dynamic_ip_found": "Je systeem gebruikt dynamische IP-adres configuratie voor interface <0>{{interfaceName}}. Om de DHCP server te gebruiken moet er een statisch IP-adres worden ingesteld. Je huidige IP-adres is <0>{{ipAddress}}. AdGuard Home zal automatisch dit IP-adres als statisch IP-adres instellen wanneer je op de knop \"DHCP inschakelen\" drukt.", "dhcp_lease_added": "Statische uitgifte \"{{key}}\" met succes toegevoegd", @@ -126,7 +127,7 @@ "top_blocked_domains": "Top geblokkeerde domeinen", "top_clients": "Top gebruikers", "no_clients_found": "Geen gebruikers gevonden", - "general_statistics": "Generieke statistieken", + "general_statistics": "Algemene statistieken", "number_of_dns_query_days": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dag", "number_of_dns_query_days_plural": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dagen", "number_of_dns_query_24_hours": "Aantal verwerkte DNS aanvragen van de laatste 24 uur", @@ -137,7 +138,7 @@ "number_of_dns_query_to_safe_search": "Aantal DNS aanvragen in zoekmachines dmv geforceerd veilig zoeken", "average_processing_time": "Gemiddelde procestijd", "average_processing_time_hint": "Gemiddelde verwerkingstijd in milliseconden van een DNS aanvraag", - "block_domain_use_filters_and_hosts": "Blokkeerd domeinen dmv filters en host bestanden", + "block_domain_use_filters_and_hosts": "Domeinen blokkeren d.m.v. filters en host-bestanden", "filters_block_toggle_hint": "Je kan blokkeringsregels toevoegen in de Filters instellingen.", "use_adguard_browsing_sec": "Gebruik AdGuardBrowsing Security web service", "use_adguard_browsing_sec_hint": "AdGuard Home controleert of het domein in de blokkeerlijst voorkomt dmv Browsing Security web service. Dit gebeurt dmv een privacy vriendelijk API verzoek:een korte prefix van de domein naam met SHA256 hash wordt verzonden naar de server.", @@ -146,15 +147,15 @@ "enforce_safe_search": "Veilig zoeken gebruiken", "enforce_save_search_hint": "AdGuard Home kan veilig zoeken forceren voor de volgende zoekmachines: Google, Youtube, Bing, en DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Geen servers gespecificeerd", - "general_settings": "Generieke instellingen", - "dns_settings": "DNS Instellingen", + "general_settings": "Algemene instellingen", + "dns_settings": "DNS instellingen", "dns_blocklists": "DNS blokkeerlijsten", "dns_allowlists": "DNS toestemmingslijsten", "dns_blocklists_desc": "AdGuard Home zal domeinen blokkeren die voorkomen in de blokkeerlijsten.", "dns_allowlists_desc": "Domeinen in de DNS toestemmingslijsten worden toegestaan zelfs al komen ze voor in de blokkeerlijsten.", "custom_filtering_rules": "Aangepaste filter regels", - "encryption_settings": "Encryptie Instellingen", - "dhcp_settings": "DHCP Instellingen", + "encryption_settings": "Encryptie instellingen", + "dhcp_settings": "DHCP instellingen", "upstream_dns": "Upstream DNS-servers", "upstream_dns_help": "Een server-adres per regel invoeren. Meer weten over het configureren van upstream DNS-servers.", "upstream_dns_configured_in_file": "Geconfigureerd in {{path}}", @@ -165,10 +166,10 @@ "enabled_filtering_toast": "Filters ingeschakeld", "disabled_safe_browsing_toast": "Veilig browsen uitgeschakeld", "enabled_safe_browsing_toast": "Veilig browsen ingeschakeld", - "disabled_parental_toast": "Ouderlijk toezicht uitgeschakeld", - "enabled_parental_toast": "Ouderlijk toezicht ingeschakeld", - "disabled_safe_search_toast": "Veilig zoeken uitgeschakeld", - "enabled_save_search_toast": "Veilig zoeken ingeschakeld", + "disabled_parental_toast": "Uitgeschakeld ouderlijk toezicht", + "enabled_parental_toast": "Ingeschakeld Ouderlijk toezicht", + "disabled_safe_search_toast": "Uitgeschakeld Veilig zoeken", + "enabled_save_search_toast": "Ingeschakeld Veilig zoeken", "enabled_table_header": "Ingeschakeld", "name_table_header": "Naam", "list_url_table_header": "URL lijst", @@ -196,35 +197,38 @@ "choose_allowlist": "Toestemmingslijsten selecteren", "enter_valid_blocklist": "Voer een geldige URL in voor de blokkeerlijst.", "enter_valid_allowlist": "Voer een geldige URL in voor de toestemmingslijst.", - "form_error_url_format": "Ongeldig URL formaat", + "form_error_url_format": "Ongeldig URL-opmaak", "form_error_url_or_path_format": "Ongeldig URL of pad van de lijst", "custom_filter_rules": "Aangepaste filterregels", "custom_filter_rules_hint": "Voer één regel op een regel in. U kunt adblock-regels gebruiken of de syntaxis van hosts-bestanden gebruiken.", "system_host_files": "Systeem host-bestanden", "examples_title": "Voorbeelden", - "example_meaning_filter_block": "blokkeer toegang tot het example.org domein en alle subdomeinen", - "example_meaning_filter_whitelist": "deblokkering van toegang tot het example.org-domein en alle bijbehorende subdomeinen", - "example_meaning_host_block": "AdGuard Home zal nu het adres 127.0.0.1 voor het domein example.org retourneren (maar niet de subdomeinen).", - "example_comment": "! Hier komt een opmerking", - "example_comment_meaning": "zomaar een opmerking", - "example_comment_hash": "# Nog een opmerking", - "example_regex_meaning": "blokkeer de toegang tot de domeinen die overeenkomen met de opgegeven reguliere expressie", - "example_upstream_regular": "standaard DNS (over UDP)", - "example_upstream_dot": "versleutelde <0>DNS-via-TLS", - "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS", - "example_upstream_doq": "versleutelde <0>DNS-via-QUIC", - "example_upstream_sdns": "je kunt <0>DNS Stamps voor <1>DNSCrypt of <2>DNS-via-HTTPS oplossingen gebruiken", - "example_upstream_tcp": "standaard DNS (over TCP)", + "example_meaning_filter_block": "blokkeer toegang tot example.org en alle subdomeinen ervan;", + "example_meaning_filter_whitelist": "deblokkeer toegang tot example.org en alle subdomeinen ervan;", + "example_meaning_host_block": "127.0.0.1 voor het domein example.org retourneren (maar niet diens subdomeinen);", + "example_comment": "! Hier komt een opmerking.", + "example_comment_meaning": "zomaar een opmerking;", + "example_comment_hash": "# Ook een opmerking.", + "example_regex_meaning": "toegang blokkeren tot de domeinen die overeenkomen met de opgegeven reguliere expressie.", + "example_upstream_regular": "standaard DNS (over UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Alle lijsten zijn reeds actueel", "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", "allow_this_client": "Toepassing/systeem toelaten", - "block_for_this_client_only": "Alleen voor deze toepassing/dit systeem blokkeren", - "unblock_for_this_client_only": "Alleen voor deze toepassing/dit systeem deblokkeren", + "block_for_this_client_only": "Alleen voor deze cliënt blokkeren", + "unblock_for_this_client_only": "Alleen voor deze cliënt deblokkeren", "time_table_header": "Tijd", "date": "Datum", "domain_name_table_header": "Domein naam", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-via-HTTPS", "dns_over_tls": "DNS-via-TLS", "dns_over_quic": "DNS-via-QUIC", - "client_id": "Apparaat-ID", - "client_id_placeholder": "Apparaat-ID invoeren", - "client_id_desc": "Verschillende apparaten kunnen worden geïdentificeerd door hun specifiek apparaat-ID. Hier vind je meer informatie over het identificeren van apparaten.", + "client_id": "Client-ID", + "client_id_placeholder": "Client-ID invoeren", + "client_id_desc": "Clients kunnen worden geïdentificeerd door hun Client-ID. Hier vind je meer informatie over het identificeren van clienten.", "download_mobileconfig_doh": ".mobileconfig voor DNS-via-HTTPS downloaden", "download_mobileconfig_dot": ".mobileconfig voor DNS-via-TLS downloaden", "download_mobileconfig": "Configuratiebestand downloaden", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Voer ratio limiet in", "rate_limit": "Ratio limiet", "edns_enable": "EDNS client subnet inschakelen", - "edns_cs_desc": "Indien ingeschakeld stuurt AdGuard Home het subnet van de client naar de DNS-servers.", + "edns_cs_desc": "De EDNS Client Subnet-optie (ECS) toevoegen aan upstream-verzoeken en de waarden die door de clients zijn verzonden registreren in het querylogboek.", "rate_limit_desc": "Het aantal verzoeken per seconde toegelaten per toestel. 0 betekent onbeperkt.", "blocking_ipv4_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek", "blocking_ipv6_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek", @@ -309,7 +313,7 @@ "install_settings_listen": "Luister interface", "install_settings_port": "Poort", "install_settings_interface_link": "De webinterface van AdGuard Home admin is beschikbaar op de volgende adressen:", - "form_error_port": "Geldige poortwaarde invoeren", + "form_error_port": "Geldig poortnummer invoeren", "install_settings_dns": "DNS-server", "install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:", "install_settings_all_interfaces": "Alle interfaces", @@ -334,8 +338,8 @@ "install_devices_router_list_4": "Je kan een DNS-server niet instellen op sommige routers. In dat geval kan het een oplossing zijn om AdGuard Home te definiëren als een <0>DHCP-server. Je kan ook in de handleiding van je router kijken hoe je een DNS-server aanpast.", "install_devices_windows_list_1": "Open het Configuratiescherm via het menu Start of Windows zoeken.", "install_devices_windows_list_2": "Ga naar de categorie Netwerk en Internet en vervolgens naar Netwerkcentrum.", - "install_devices_windows_list_3": "Zoek aan de linkerkant van het scherm \"Adapter-instellingen wijzigen\" en klik erop.", - "install_devices_windows_list_4": "Selecteer jouw actieve verbinding, klik er met de rechtermuisknop op en kies Eigenschappen.", + "install_devices_windows_list_3": "Aan de linkerkant van het scherm, klik op \"Adapter-instellingen wijzigen\".", + "install_devices_windows_list_4": "Klik met de rechtermuisknop op jouw actieve verbinding en kies Eigenschappen.", "install_devices_windows_list_5": "Zoek \"Internet Protocol versie 4 (TCP/IPv4)\" (of, voor IPv6, \"Internet Protocol versie 6 (TCP/IPv6)\") in de lijst, selecteer het en klik vervolgens opnieuw op Eigenschappen.", "install_devices_windows_list_6": "Kies \"Gebruik de volgende DNS-serveradressen\" en voer jouw AdGuard Home serveradressen in.", "install_devices_macos_list_1": "Klik op het Apple-pictogram en ga naar Systeemvoorkeuren.", @@ -360,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", - "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.", @@ -378,18 +382,18 @@ "encryption_key_input": "Kopieër en plak je PEM-gecodeerde prive sleutel voor je certificaat hier.", "encryption_enable": "Activeer encryptie (HTTPS, DNS-via-HTTPS, en DNS-via-TLS)", "encryption_enable_desc": "Als encryptie is geactiveerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en de DNS-server zal luisteren naar aanvragen via DNS-via-HTTPS en DNS-via-TLS.", - "encryption_chain_valid": "certificaatketen is geldig", - "encryption_chain_invalid": "certificaatketen is ongeldig", - "encryption_key_valid": "Dit is een geldig {{type}} privé sleutel", - "encryption_key_invalid": "Dit is een ongeldig {{type}} privé sleutel", + "encryption_chain_valid": "Certificaatketen is geldig", + "encryption_chain_invalid": "Certificaatketen is ongeldig", + "encryption_key_valid": "Dit is een geldige {{type}} privésleutel", + "encryption_key_invalid": "Dit is een ongeldige {{type}} privésleutel", "encryption_subject": "Onderwerp", "encryption_issuer": "Uitgever", "encryption_hostnames": "Hostnamen", "encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?", "topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen bij.", "topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen bij.", - "form_error_port_range": "Poort nummer invoeren tussen 80 en 65535", - "form_error_port_unsafe": "Dit is een onveilige poort", + "form_error_port_range": "Poortnummer invoeren tussen 80 en 65535", + "form_error_port_unsafe": "Onveilige poort", "form_error_equal": "Mag niet gelijk zijn", "form_error_password": "Wachtwoord komt niet overeen", "reset_settings": "Reset Instellingen", @@ -397,16 +401,16 @@ "setup_guide": "Installatie gids", "dns_addresses": "DNS adressen", "dns_start": "DNS-server aan het opstarten", - "dns_status_error": "Fout bij het oproepen van de DNS-server status", + "dns_status_error": "Fout bij het controleren van de DNS-server status", "down": "Uitgeschakeld", "fix": "Los op", "dns_providers": "hier is een <0>lijst of gekende DNS providers waarvan je kan kiezen.", - "update_now": "Update nu", + "update_now": "Nu bijwerken", "update_failed": "Automatisch bijwerken is mislukt. Volg deze stappen om handmatig bij te werken.", "manual_update": "Volg deze stappen om handmatig bij te werken.", "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt", - "clients_title": "Gebruikers", - "clients_desc": "Configureer apparaten die gebruik maken van AdGuard Home", + "clients_title": "Permanente clients", + "clients_desc": "Permanente client-records configureren voor apparaten verbonden met AdGuard Home", "settings_global": "Globaal", "settings_custom": "Aangepast", "table_client": "Gebruiker", @@ -417,7 +421,7 @@ "client_edit": "Wijzig gebruiker", "client_identifier": "Identificeer via", "ip_address": "IP adres", - "client_identifier_desc": "Apparaten kunnen worden geïdentificeerd door hun IP-adres, CIDR, MAC-adres of een speciaal apparaat-ID (kan gebruikt worden voor DoT/DoH/DoQ). <0>Hier kan je meer lezen over het identificeren van apparaten.", + "client_identifier_desc": "Cliënten kunnen worden geïdentificeerd door hun IP-adres, CIDR, MAC-adres of Client-ID (kan gebruikt worden voor DoT/DoH/DoQ). <0>Hier kan je meer lezen over het identificeren van cliënten.", "form_enter_ip": "Vul IP in", "form_enter_subnet_ip": "Voer een IP-adres in voor het subnet “{{cidr}}”", "form_enter_mac": "Vul MAC in", @@ -432,18 +436,18 @@ "clients_not_found": "Geen gebruikers gevonden", "client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?", "list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?", - "auto_clients_title": "Gebruikers (runtime)", - "auto_clients_desc": "Data over gebruikers die AdGuard Home gebruiken, maar niet geconfigureerd zijn", + "auto_clients_title": "Runtime-clients", + "auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken", "access_title": "Toegangs instellingen", - "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen.", + "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen", "access_allowed_title": "Toegestane gebruikers", - "access_allowed_desc": "Een lijst met CIDR's, IP-adressen of client-ID's. Indien geconfigureerd, accepteert AdGuard Home alleen verzoeken van deze cliënts.", + "access_allowed_desc": "Een lijst met CIDR's, IP-adressen of Client-ID's. Indien geconfigureerd, accepteert AdGuard Home alleen verzoeken van deze cliënts.", "access_disallowed_title": "Verworpen gebruikers", - "access_disallowed_desc": "Een lijst met CIDR's, IP-adressen of client-ID's. Indien geconfigureerd, zal AdGuard Home verzoeken van deze klanten verwerpen. Als toegestane cliënts zijn geconfigureerd, wordt dit veld genegeerd.", + "access_disallowed_desc": "Een lijst met CIDR's, IP-adressen of Client-ID's. Indien geconfigureerd, zal AdGuard Home verzoeken van deze klanten verwerpen. Als toegestane cliënts zijn geconfigureerd, wordt dit veld genegeerd.", "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", @@ -475,8 +479,8 @@ "dns_rewrites": "DNS herschrijvingen", "form_domain": "Vul domein of wildcard in", "form_answer": "Vul IP adres of domeinnaam in", - "form_error_domain_format": "Ongeldige domeinnaam", - "form_error_answer_format": "Ongeldig antwoord", + "form_error_domain_format": "Ongeldige opmaak domein", + "form_error_answer_format": "Ongeldige opmaak antwoord", "configure": "Bewerk", "main_settings": "Algemene instellingen", "block_services": "Specifieke services blokkeren", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dagen", "interval_days_plural": "{{count}} dagen", "domain": "Domein", + "ecs": "ECS", "punycode": "Punycode", "answer": "Antwoord", "filter_added_successfully": "De lijst is succesvol toegevoegd", @@ -507,7 +512,7 @@ "filter_updated": "De lijst is succesvol geüpdatet", "statistics_configuration": "Statistieken configuratie", "statistics_retention": "Statistieken retentie", - "statistics_retention_desc": "Als je de interval waarde vermindert, zullen sommige gegevens verloren gaan", + "statistics_retention_desc": "Als je de intervalwaarde vermindert, zullen sommige gegevens verloren gaan", "statistics_clear": "Statistieken wissen", "statistics_clear_confirm": "Alle statistieken werkelijk wissen?", "statistics_retention_confirm": "Weet u zeker dat u de bewaartermijn van de statistieken wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} uren", "filters_configuration": "Filters instellingen", "filters_enable": "Filters inschakelen", - "filters_interval": "Filters update frequentie", + "filters_interval": "Filter update frequentie", "disabled": "Uitgeschakeld", "username_label": "Gebruikersnaam", "username_placeholder": "Voer gebruikersnaam in", @@ -552,10 +557,10 @@ "autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener <0> DNS-serveradres instellen op 127.0.0.1 <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf <0> Stop DNSStubListener (herlaad systemd-resolved service) ", "autofix_warning_result": "Als gevolg hiervan worden alle DNS-verzoeken van je systeem standaard door AdGuard Home verwerkt.", "tags_title": "Labels", - "tags_desc": "Je kunt tags selecteren die overeenkomen met de client. Tags kunnen worden opgenomen in de filterregels en je kunt ze dan nauwkeuriger toepassen. <0> Meer informatie ", + "tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie.", "form_select_tags": "Client tags selecteren", "check_title": "Controleer de filtering", - "check_desc": "Controleer of de hostnaam wordt gefilterd", + "check_desc": "Controleren of een hostnaam wordt gefilterd.", "check": "Controleren", "form_enter_host": "Voer een hostnaam in", "filtered_custom_rules": "Gefilterd door aangepaste filterregels", @@ -598,14 +603,14 @@ "blocklist": "Blokkeerlijst", "milliseconds_abbreviation": "ms", "cache_size": "Cache grootte", - "cache_size_desc": "DNS cache grootte (in bytes)", + "cache_size_desc": "DNS-cache grootte (in bytes).", "cache_ttl_min_override": "Minimale TTL overschrijven", "cache_ttl_max_override": "Maximale TTL overschrijven", "enter_cache_size": "Cache grootte invoeren (bytes)", "enter_cache_ttl_min_override": "Minimum TTL invoeren (seconden)", "enter_cache_ttl_max_override": "Maximum TTL invoeren (seconden)", - "cache_ttl_min_override_desc": "Uitbreiden van korte Time-To-Live waardes (seconden) ontvangen van de upstream server bij het cachen van DNS antwoorden", - "cache_ttl_max_override_desc": "Instellen van maximum time-to-live waarde (seconden) voor opslag in de DNS cache", + "cache_ttl_min_override_desc": "Uitbreiden van korte Time-To-Live waardes (seconden) ontvangen van de upstream server bij het cachen van DNS antwoorden.", + "cache_ttl_max_override_desc": "Instellen van maximum time-to-live waarde (seconden) voor opslag in de DNS cache.", "ttl_cache_validation": "Minimale waarde TTL-cache moet kleiner dan of gelijk zijn aan de maximale waarde", "cache_optimistic": "Optimistisch cachen", "cache_optimistic_desc": "Laat AdGuard Home reageren vanuit de cache, zelfs als de vermeldingen zijn verlopen en probeer deze ook te vernieuwen.", @@ -621,12 +626,12 @@ "original_response": "Oorspronkelijke reactie", "click_to_view_queries": "Klik om queries te bekijken", "port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie hoe dit is op te lossen.", - "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS verzoeken van deze toepassing/dit systeem negeren.", + "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS-verzoeken van deze cliënt laten vervallen.", "filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.", "last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.", - "experimental": "Experimenteel", "use_saved_key": "De eerder opgeslagen sleutel gebruiken", "parental_control": "Ouderlijk toezicht", "safe_browsing": "Veilig browsen", - "served_from_cache": "{{value}} (geleverd vanuit cache)" + "served_from_cache": "{{value}} (geleverd vanuit cache)", + "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn" } diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 4bac8cee..7ff4664f 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -10,6 +10,10 @@ "bootstrap_dns_desc": "Bootstrap-DNS-tjenere brukes til å oppklare IP-adressene til DoH/DoT-oppklarerene som du har valgt som oppstrømstjenere.", "local_ptr_title": "Private DNS-tjenere", "local_ptr_desc": "DNS-tjenerne som AdGuard Home bruker for lokale PTR-spørringer. Disse tjenerne brukes til å løse vertsnavnene til klienter med private IP-adresser, for eksempel \"192.168.12.34\", ved bruk av omvendt DNS. Hvis det ikke er angitt, bruker AdGuard Home adressene til standard-DNS-løserne til operativsystemet ditt, bortsett fra adressene til selve AdGuard Home.", + "local_ptr_default_resolver": "Som standard, bruker AdGuard Home følgende revers-DNS-oppletere: {{ip}}.", + "local_ptr_no_default_resolver": "AdGuard Home klarte ikke å finne egnede private revers-DNS-oppletere for dette systemet.", + "local_ptr_placeholder": "Skriv inn én tjeneradresse per linje", + "resolve_clients_title": "Skru på revers-oppleting av klienters IP-adresser", "use_private_ptr_resolvers_title": "Bruk private omvendte DNS-løsere", "check_dhcp_servers": "Se etter DHCP-tjenere", "save_config": "Lagre oppsettet", @@ -21,20 +25,24 @@ "dhcp_description": "Dersom ruteren din ikke har DHCP-innstillinger, kan du bruke AdGuard Home sin egen innebygde DHCP-tjener.", "dhcp_enable": "Skru på DHCP-tjeneren", "dhcp_disable": "Skru av DHCP-tjeneren", + "dhcp_not_found": "Det er trygt å skru på den innebygde DHCP-tjeneren - vi kunne ikke finne noen aktive DHCP-tjenere i nettverket. Men vi oppfordrer deg til å dobbeltsjekke manuelt, siden vår automatiske test ikke gir 100% sikre svar ennå.", "dhcp_found": "En aktiv DHCP-tjener ble oppdaget i nettverket. Det er ikke trygt å bruke den innebygde DHCP-tjeneren.", "dhcp_leases": "DHCP-leieavtaler", "dhcp_static_leases": "Statiske DHCP-leieavtaler", "dhcp_leases_not_found": "Ingen DHCP-leieavtaler ble funnet", + "dhcp_config_saved": "Lagret DHCP-tjeneroppsettet", "dhcp_ipv4_settings": "DHCP IPv4-innstillinger", "dhcp_ipv6_settings": "DHCP IPv6-innstillinger", "form_error_required": "Påkrevd felt", "form_error_ip4_format": "Ugyldig IPv4-format", "form_error_ip6_format": "Ugyldig IPv6-format", - "form_error_ip_format": "Ugyldig IP-adresse", + "form_error_ip_format": "Ugyldig IPv4-format", "form_error_mac_format": "Ugyldig MAC-format", "form_error_client_id_format": "Ugyldig ID-klientformat", "form_error_server_name": "Ugyldig tjenernavn", + "form_error_subnet": "Undernettet «{{cidr}}» inneholder ikke IP-adressen «{{ip}}»", "form_error_positive": "Må være høyere enn 0", + "greater_range_start_error": "Må være høyere enn rekkeviddens start", "dhcp_form_gateway_input": "Gateway-IP", "dhcp_form_subnet_input": "Nettverksmaske", "dhcp_form_range_title": "Spennvidden til IP-adressene", @@ -48,11 +56,16 @@ "ip": "IP-adresse", "dhcp_table_hostname": "Vertsnavn", "dhcp_table_expires": "Utløper", + "dhcp_warning": "Hvis du vil aktivere DHCP-tjeneren likevel, så sørg for at det ikke er noen andre aktive DHCP-tjenere i nettverket ditt. Ellers kan det knekke internettilgangen til tilkoblede enheter!", + "dhcp_error": "Vi klarte ikke å fastslå om det er en annen DHCP-tjener i nettverket ditt eller ikke.", + "dhcp_static_ip_error": "For å kunne bruke DHCP-tjeneren, må det være satt en statisk IP-adresse. Vi klarte ikke å finne ut om dette nettverksgrensesnittet har blitt satt opp med en statisk IP-adresse. Vennligst sett opp en statisk IP-adresse manuelt.", + "dhcp_dynamic_ip_found": "Systemet ditt bruker et oppsett med dynamisk IP-adresse for grensesnittet <0>{{interfaceName}}. For å kunne bruke DHCP-tjeneren, må en statisk IP-adresse ha blitt satt opp. Din nåværende IP-adresse er <0>{{ipAddress}}. Vi vil automatisk gjøre denne IP-adressen statisk hvis du trykker på «Skru på DHCP»-knappen.", "dhcp_lease_added": "Den statiske leieavtalen «{{key}}» ble vellykket lagt til", "dhcp_lease_deleted": "Den statiske leieavtalen «{{key}}» ble vellykket lagt slettet", "dhcp_new_static_lease": "Ny statisk leieavtale", "dhcp_static_leases_not_found": "Ingen statiske DHCP-leieavtaler ble funnet", "dhcp_add_static_lease": "Legg til statisk leieavtale", + "dhcp_reset": "Er du sikker på at du vil tilbakestille DHCP-oppsettet?", "country": "Land", "city": "By", "delete_confirm": "Er du sikker på at du vil slette «{{key}}»?", @@ -91,15 +104,24 @@ "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", "no_domains_found": "Ingen domener ble funnet", "requests_count": "Antall forespørsler", "top_blocked_domains": "Mest blokkerte domener", "top_clients": "Vanligste klienter", "no_clients_found": "Ingen klienter ble funnet", "general_statistics": "Generelle statistikker", + "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", + "number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul", + "number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert", "enforced_save_search": "Påtvungede barnevennlige søk", + "number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget", "average_processing_time": "Gjennomsnittlig behandlingstid", "average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder", "block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener", @@ -177,10 +199,11 @@ "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", + "updated_upstream_dns_toast": "Oppdaterte oppstrøms-DNS-tjenerne", "dns_test_ok_toast": "De spesifiserte DNS-tjenerne fungerer riktig", "dns_test_not_ok_toast": "Tjeneren «{{key}}» kunne ikke brukes, vennligst dobbeltsjekk at du har skrevet den riktig", "unblock": "Tillat", @@ -241,13 +264,16 @@ "dns_over_quic": "DNS-over-QUIC", "client_id": "Klient-ID", "client_id_placeholder": "Skriv inn klient-ID", + "client_id_desc": "Forskjellige klienter kan identifiserer med en spesiell klient-ID. Her kan du lære mer om hvordan man identifiserer klienter.", "download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS", "download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS", + "download_mobileconfig": "Last ned oppsettsfil", "plain_dns": "Ordinær DNS", "form_enter_rate_limit": "Skriv inn forespørselsfrekvensgrense", "rate_limit": "Forespørselsfrekvensgrense", "edns_enable": "Aktiver EDNS-klientundernett", "edns_cs_desc": "Hvis det er skrudd på, vil AdGuard Home sende klientenes undernett til DNS-tjenerne.", + "rate_limit_desc": "Antallet forespørsler per sekund som én enkelt klient har lov til å be om (0: ubegrenset)", "blocking_ipv4_desc": "IP-adressen som det skal svares med for blokkerte A-forespørsler", "blocking_ipv6_desc": "IP-adressen som det skal svares med for blokkerte AAAA-forespørsler", "blocking_mode_default": "Standard: Svar med null-IP-adresse (0.0.0.0 for A; :: for AAAA) når den blokkeres av adblock-aktige oppføringer; svar med IP-adressen som er spesifisert i oppføringen når den blokkeres av /etc/hosts-typeoppføringer", @@ -289,8 +315,10 @@ "install_devices_router": "Ruter", "install_devices_router_desc": "Dette oppsettet vil automatisk dekke alle enhetene som er koblet til hjemmeruteren din, og du vil ikke måtte sette opp hver av dem manuelt.", "install_devices_address": "AdGuard Home-DNS-tjeneren lytter til de følgende adressene", + "install_devices_router_list_1": "Åpne innstillingene til ruteren din. Vanligvis kan du få tilgang til den på nettleseren din gjennom en URL (f.eks. http://192.168.0.1/ eller http://192.168.1.1/). Du kan bli spurt om å skrive inn passordet ditt. Hvis du ikke husker det, kan du som oftest tilbakestille passordet ditt ved å trykke på knapp på selve ruteren. Noen rutere krever et spesifikt program, som i så fall er ment å allerede ha blitt installert på din PC/mobil.", "install_devices_router_list_2": "Finn DHCP-/DNS-innstillingene. Se etter DNS-bokstavene ved siden av et felt som tillater to eller tre sett med sifre, som hver er delt opp i fire grupper på 1-3 sifre.", "install_devices_router_list_3": "Skriv inn din AdGuard Home-tjeners adresser her.", + "install_devices_router_list_4": "På noen rutertyper, f.eks. Altibox sine hjemmesentraler, kan man ikke velge en selvvalgt DNS-tjener. I så fall kan det hjelpe på saken om du setter opp AdGuard Home som en <0>DHCP-tjener. Alternativt, burde du se i bruksanvisningen til din spesifikke rutermodell om hvordan man tilpasser DNS-tjenerne.", "install_devices_windows_list_1": "Åpne «Kontrollpanel» gjennom Start-menyen eller et Windows-søk.", "install_devices_windows_list_2": "Gå til «Nettverk og internett»-kategorien, og så til «Nettverks- og delingssenter».", "install_devices_windows_list_3": "På den venstre siden av skjermen, finn «Endre innstillinger for nettverkskort» og klikk på den.", @@ -316,17 +344,18 @@ "install_saved": "Lagringen var vellykket", "encryption_title": "Kryptering", "encryption_desc": "Krypteringsstøtte (HTTPS/TLS) for både DNS og admin-nettgrensesnittet", + "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.", @@ -346,7 +375,9 @@ "encryption_reset": "Er du sikker på at du vil tilbakestille krypteringsinnstillingene?", "topline_expiring_certificate": "Ditt SSL-sertifikat er i ferd med å utløpe. Oppdater <0>Krypteringsinnstillinger.", "topline_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger.", + "form_error_port_range": "Skriv inn et portnummer i området 80-65535", "form_error_port_unsafe": "Denne porten er ikke trygg", + "form_error_equal": "Burde ikke være de samme", "form_error_password": "Passordet samsvarer ikke", "reset_settings": "Tilbakestill innstillinger", "update_announcement": "AdGuard Home {{version}} er nå tilgjengelig! <0>Klikk her for mere informasjon.", @@ -374,6 +405,7 @@ "ip_address": "IP-adresse", "client_identifier_desc": "Klienter kan bli identifisert gjennom IP-adressen, CIDR, MAC-adressen, eller en spesiell klient-ID (kan også brukes for DoT/DoH/DoQ). <0>Her kan du lære mer om å identifisere klienter.", "form_enter_ip": "Skriv inn IP", + "form_enter_subnet_ip": "Skriv inn en IP-adresse i undernettet «{{cidr}}»", "form_enter_mac": "Skriv inn MAC", "form_enter_id": "Skriv inn identifikator", "form_add_id": "Legg til identifikator", @@ -397,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", @@ -416,6 +448,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy støtter <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox støtter <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Du finner flere implementeringer <0>her og <1>her.", + "setup_dns_privacy_ioc_mac": "iOS- og macOS-oppsett", "setup_dns_notice": "For å benytte <1>DNS-over-HTTPS eller <1>DNS-over-TLS, må du <0>sette opp Kryptering i AdGuard Home-innstillingene.", "rewrite_added": "DNS-omdirigeringen for «{{key}}» ble vellykket lagt til", "rewrite_deleted": "DNS-omdirigeringen for «{{key}}» ble vellykket slettet", @@ -453,6 +486,7 @@ "interval_days": "{{count}} dag", "interval_days_plural": "{{count}} dager", "domain": "Domene", + "punycode": "Punycode", "answer": "Svar", "filter_added_successfully": "Filteret har blitt vellykket lagt til", "filter_removed_successfully": "Listen ble vellykket fjernet", @@ -499,6 +533,7 @@ "disable_ipv6": "Skru av IPv6", "disable_ipv6_desc": "Hvis dette er skrudd på, vil alle DNS-forespørslene til IPv6-adresser (AAAA-type) bli droppet.", "fastest_addr": "Raskeste IP-adresse", + "fastest_addr_desc": "Forespør alle DNS-tjenerne og hent den raskeste IP-adressen blant alle svarene. Dette vil gjøre DNS-forespørslene tregere, siden vi må vente på svar fra alle DNS-tjenerne, men det vil forbedre tilkoblingen generelt.", "autofix_warning_text": "Hvis du klikker på «Fiks», vil AdGuard Home sette opp systemet ditt til å bruke 'AdGuard Home'-DNS-tjeneren.", "autofix_warning_list": "Den vil utføre disse handlingene: <0>Skru av systemets DNSStubListener <0>Sette DNS-tjeneradressen til 127.0.0.1 <0>Bytte ut det symbolske lenkemålet til /etc/resolv.conf med /run/systemd/resolve/resolv.conf <0>Stoppe DNSStubListener (gjeninnlast 'systemd-resolved'-tjenesten)", "autofix_warning_result": "Som følge av det vil alle DNS-forespørsler fra systemet ditt bli behandlet av AdGuard Home som standard.", @@ -528,6 +563,7 @@ "set_static_ip": "Velg en statisk IP-adresse", "install_static_ok": "Gode nyheter! Den statiske IP-adressen er allerede satt opp", "install_static_error": "AdGuard Home kan ikke sette opp automatisk i dette nettverksgrensesnitt. Vennligst let opp anvisningen for hvordan man gjør det manuellt.", + "install_static_configure": "Vi har oppdaget at det brukes en dynamisk IP-adresse — <0>{{ip}}. Vil du bruke det som din statiske adresse?", "confirm_static_ip": "AdGuard Home vil sette opp {{ip}} til å bli din statiske IP-adresse. Vil du fortsette?", "list_updated": "{{count}} liste oppdatert", "list_updated_plural": "{{count}} lister oppdatert", @@ -539,7 +575,7 @@ "show_whitelisted_responses": "Hvitelistet", "show_processed_responses": "Bearbeidet", "blocked_safebrowsing": "Blokkert av barnevennlig nettlesing", - "blocked_adult_websites": "Blokkert av foreldrekontroll", + "blocked_adult_websites": "Blokkerte voksennettsteder", "blocked_threats": "Blokkerte trusler", "allowed": "Unntak", "filtered": "Filtrert", @@ -567,12 +603,13 @@ "filter_category_security_desc": "Lister som spesialiserer seg på å blokkere skadevare-, phishing- eller svindeldomener", "filter_category_regional_desc": "Lister som fokuserer på regionale reklamer og sporingstjenere", "filter_category_other_desc": "Andre blokkeringslister", + "setup_config_to_enable_dhcp_server": "Oppsett for å skru på DHCP-tjeneren", "original_response": "Opprinnelig svar", "click_to_view_queries": "Klikk for å vise forespørsler", "port_53_faq_link": "Port 53 er ofte opptatt av «DNSStubListener»- eller «systemd-resolved»-tjenestene. Vennligst les <0>denne instruksjonen om hvordan man løser dette.", "adg_will_drop_dns_queries": "AdGuard Home vil droppe alle DNS-forespørsler fra denne klienten.", - "experimental": "Eksperimentell", "use_saved_key": "Bruk den tidligere lagrede nøkkelen", "parental_control": "Foreldrekontroll", + "safe_browsing": "Sikker surfing", "served_from_cache": "{{value}} (formidlet fra mellomlageret)" } diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index 19cf6aba..65be442a 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -1,7 +1,7 @@ { "client_settings": "Ustawienia klienta", - "example_upstream_reserved": "Możesz określić nadrzędny serwer DNS <0>dla określonych domen", - "example_upstream_comment": "Możesz podać komentarz", + "example_upstream_reserved": "upstream <0>dla określonych domen;", + "example_upstream_comment": "komentarz.", "upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.", "parallel_requests": "Równoległe żądania", "load_balancing": "Równoważenie obciążenia", @@ -35,7 +35,7 @@ "dhcp_config_saved": "Konfiguracja DHCP została pomyślnie zapisana", "dhcp_ipv4_settings": "Ustawienia serwera DHCP IPv4", "dhcp_ipv6_settings": "Ustawienia serwera DHCP IPv6", - "form_error_required": "Pole jest wymagane", + "form_error_required": "Pole wymagane", "form_error_ip4_format": "Nieprawidłowy adres IPv4", "form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu", "form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Nieprawidłowy adres IPv6", "form_error_ip_format": "Nieprawidłowy adres IP", "form_error_mac_format": "Nieprawidłowy adres MAC", - "form_error_client_id_format": "ID klienta musi zawierać tylko cyfry, małe litery i myślniki", + "form_error_client_id_format": "ClientID musi zawierać tylko cyfry, małe litery i myślniki", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nazwa hosta", "dhcp_table_expires": "Wygasa", "dhcp_warning": "Jeśli mimo wszystko chcesz włączyć serwer DHCP, upewnij się, że w Twojej sieci nie ma innego aktywnego serwera DHCP, ponieważ może to spowodować przerwanie łączności z Internetem dla urządzeń w sieci!", - "dhcp_error": "AdGuard Home nie mógł określić, czy w sieci jest inny aktywny serwer DHCP.", + "dhcp_error": "AdGuard Home nie mógł określić, czy w sieci jest inny aktywny serwer DHCP", "dhcp_static_ip_error": "Aby korzystać z serwera DHCP musi być ustawiony statyczny adres IP. AdGuard Home nie udało się ustalić, czy ten interfejs sieciowy jest skonfigurowany przy użyciu statycznego adresu IP. Proszę ustawić statyczny adres IP ręcznie.", "dhcp_dynamic_ip_found": "Twój system używa dynamicznej konfiguracji adresu IP dla interfejsu <0>{{interfaceName}}. Aby można było korzystać z serwera DHCP, należy ustawić statyczny adres IP. Twój obecny adres IP to <0>{{ipAddress}}. AdGuard Home automatycznie ustawi ten adres IP jako statyczny, jeśli naciśniesz przycisk \"Włącz serwer DHCP\".", "dhcp_lease_added": "Dzierżawa statyczna \"{{key}}\" pomyślnie dodana", @@ -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", @@ -167,8 +168,8 @@ "enabled_safe_browsing_toast": "Włączone Bezpieczne przeglądanie", "disabled_parental_toast": "Wyłączona Kontrola Rodzicielska", "enabled_parental_toast": "Włączona Kontrola Rodzicielska", - "disabled_safe_search_toast": "Bezpieczne wyszukiwanie zostało włączone", - "enabled_save_search_toast": "Bezpieczne wyszukiwanie zostało włączone", + "disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie", + "enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie", "enabled_table_header": "Włączone", "name_table_header": "Nazwa", "list_url_table_header": "Adres URL listy", @@ -196,29 +197,32 @@ "choose_allowlist": "Wybierz listy dozwolonych", "enter_valid_blocklist": "Wpisz prawidłowy adres URL do listy zablokowanych.", "enter_valid_allowlist": "Wpisz prawidłowy adres URL do listy dozwolonych.", - "form_error_url_format": "Format adresu URL jest nieprawidłowy", - "form_error_url_or_path_format": "Adres URL lub bezwzględna ścieżka listy jest nieprawidłowa", + "form_error_url_format": "Nieprawidłowy format URL", + "form_error_url_or_path_format": "Nieprawidłowy adres URL lub bezwzględna ścieżka listy", "custom_filter_rules": "Niestandardowe reguły filtrowania", "custom_filter_rules_hint": "Wpisz jedną regułę w jednej linii. Możesz użyć reguł adblock lub składni plików hostów.", "system_host_files": "Pliki hosts systemu", "examples_title": "Przykłady", - "example_meaning_filter_block": "zablokuj dostęp do domeny example.org i wszystkich jej subdomen", - "example_meaning_filter_whitelist": "odblokuj dostęp do domeny example.org i wszystkich jej subdomen", - "example_meaning_host_block": "AdGuard Home zwróci adres 127.0.0.1 dla domeny example.org (ale nie jej subdomen).", - "example_comment": "! Tutaj jest komentarz", - "example_comment_meaning": "komentarz", - "example_comment_hash": "# Również komentarz", - "example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego", - "example_upstream_regular": "normalny DNS (przez UDP)", - "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS", - "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS", - "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC", - "example_upstream_sdns": "możesz użyć adresu <0>DNS Stamps dla protokołu <1>DNSCrypt lub <2>DNS-over-HTTPS", - "example_upstream_tcp": "zwykły DNS (przez TCP)", + "example_meaning_filter_block": "zablokuj dostęp do domeny example.org i wszystkich jej subdomen;", + "example_meaning_filter_whitelist": "odblokuj dostęp do domeny example.org i wszystkich jej subdomen;", + "example_meaning_host_block": "odpowiedz 127.0.0.1 na example.org (ale nie dla jego subdomen);", + "example_comment": "! Tutaj jest komentarz.", + "example_comment_meaning": "komentarz;", + "example_comment_hash": "# Również komentarz.", + "example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego.", + "example_upstream_regular": "normalny DNS (przez UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Wszystkie listy są już aktualne", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID klienta", - "client_id_placeholder": "Wpisz ID klienta", - "client_id_desc": "Różnych klientów można zidentyfikować za pomocą specjalnego ID klienta. Tutaj możesz dowiedzieć się więcej o tym, jak identyfikować klientów.", + "client_id": "ClientID", + "client_id_placeholder": "Wpisz ClientID", + "client_id_desc": "Klienci mogą być identyfikowani przez ClientID. Dowiedz się więcej o tym, jak identyfikować klientów tutaj.", "download_mobileconfig_doh": "Pobierz plik .mobileconfig dla DNS-over-HTTPS", "download_mobileconfig_dot": "Pobierz plik .mobileconfig dla DNS-over-TLS", "download_mobileconfig": "Pobierz plik konfiguracyjny", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Wpisz limit ilościowy", "rate_limit": "Limit ilościowy", "edns_enable": "Włącz podsieć klienta EDNS", - "edns_cs_desc": "Wyślij podsieci klientów do serwerów DNS.", + "edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.", "rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.", "blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A", "blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA", @@ -334,8 +338,8 @@ "install_devices_router_list_4": "Na niektórych typach routerów nie można skonfigurować własnego serwera DNS. W takim przypadku pomocne może być skonfigurowanie AdGuard Home jako <0>serwera DHCP. W przeciwnym razie należy sprawdzić w instrukcji obsługi routera, jak dostosować serwery DNS do konkretnego modelu routera.", "install_devices_windows_list_1": "Otwórz panel Ustawienia w menu Start lub w Windows.", "install_devices_windows_list_2": "Przejdź do kategorii Sieć i Internet, a następnie do Centrum sieci i udostępniania.", - "install_devices_windows_list_3": "Po lewej stronie ekranu znajdź \"Zmień ustawienia adaptera\" i kliknij na niego.", - "install_devices_windows_list_4": "Wybierz aktywne połączenie, kliknij je prawym przyciskiem myszy i wybierz Właściwości.", + "install_devices_windows_list_3": "W lewym panelu kliknij \"Zmień ustawienia adaptera\".", + "install_devices_windows_list_4": "Kliknij prawym przyciskiem myszy aktywne połączenie i wybierz Właściwości.", "install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij na Właściwości.", "install_devices_windows_list_6": "Wybierz opcję \"Użyj następujących adresów serwerów DNS\" i wprowadź adresy serwerów AdGuard Home.", "install_devices_macos_list_1": "Kliknij ikonę Apple i przejdź do Preferencje systemowe.", @@ -360,7 +364,7 @@ "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -380,8 +384,8 @@ "encryption_enable_desc": "Jeśli szyfrowanie jest włączone, interfejs administracyjny AdGuard Home będzie działał przez HTTPS, a serwer DNS będzie nasłuchiwał żądań przez DNS-over-HTTPS i DNS-over-TLS.", "encryption_chain_valid": "Łańcuch certyfikatów jest prawidłowy", "encryption_chain_invalid": "Łańcuch certyfikatu jest nieprawidłowy", - "encryption_key_valid": "Poprawny {{type}} klucz prywatny.", - "encryption_key_invalid": "Klucz prywatny {{type}} jest nieprawidłowy", + "encryption_key_valid": "Poprawny {{type}} klucz prywatny", + "encryption_key_invalid": "Nieprawidłowy {{type}} klucz prywatny", "encryption_subject": "Temat", "encryption_issuer": "Zgłaszający", "encryption_hostnames": "Nazwy hostów", @@ -389,15 +393,15 @@ "topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania.", "topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania.", "form_error_port_range": "Wpisz numer portu z zakresu 80-65535", - "form_error_port_unsafe": "To jest niebezpieczny port", + "form_error_port_unsafe": "Niebezpieczny port", "form_error_equal": "Nie mogą być równe", - "form_error_password": "Hasło nie pasuje", + "form_error_password": "Niezgodne hasło", "reset_settings": "Resetowanie ustawień", "update_announcement": "AdGuard Home {{version}} jest już dostępny! <0>Kliknij tutaj aby uzyskać więcej informacji.", "setup_guide": "Przewodnik instalacji", "dns_addresses": "Adresy DNS", "dns_start": "Serwer DNS uruchamia się", - "dns_status_error": "Błąd uzyskania statusu serwera DNS", + "dns_status_error": "Błąd podczas sprawdzania stanu serwera DNS", "down": "Utrata połączenia", "fix": "Napraw", "dns_providers": "Oto lista <0>znanych dostawców DNS do wyboru.", @@ -405,8 +409,8 @@ "update_failed": "Automatyczna aktualizacja nie powiodła się. Proszę wykonaj kroki aby zaktualizować ręcznie.", "manual_update": "Proszę wykonać te czynności, aby zaktualizować ręcznie.", "processing_update": "Poczekaj, trwa aktualizacja AdGuard Home", - "clients_title": "Klienci", - "clients_desc": "Skonfiguruj urządzenia podłączone do AdGuard Home", + "clients_title": "Trwali klienci", + "clients_desc": "Skonfiguruj trwałe rekordy klienta dla urządzeń podłączonych do AdGuard Home", "settings_global": "Globalny", "settings_custom": "Własne", "table_client": "Klient", @@ -417,7 +421,7 @@ "client_edit": "Edytuj klienta", "client_identifier": "Identyfikator", "ip_address": "Adres IP", - "client_identifier_desc": "Klientów można zidentyfikować po adresie IP, CIDR, adresie MAC lub specjalnym identyfikatorze klienta (może służyć do DoT/DoH/DoQ). <0>Tutaj możesz dowiedzieć się więcej o tym, jak identyfikować klientów.", + "client_identifier_desc": "Klienci mogą być identyfikowani na podstawie ich adresu IP, CIDR, adresu MAC lub ClientID (może być używany do DoT/DoH/DoQ). Dowiedz się więcej o tym, jak identyfikować klientów <0>tutaj.", "form_enter_ip": "Wpisz adres IP", "form_enter_subnet_ip": "Wprowadź adres IP w podsieci \"{{cidr}}\"", "form_enter_mac": "Wpisz adres MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Nie znaleziono klientów", "client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?", "list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?", - "auto_clients_title": "Klienci (czas uruchamiania)", - "auto_clients_desc": "Dane klientów, które używają AdGuard Home, ale nie są przechowywane w konfiguracji", + "auto_clients_title": "Uruchomieni klienci", + "auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home", "access_title": "Ustawienia dostępu", - "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home.", + "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home", "access_allowed_title": "Dozwoleni klienci", - "access_allowed_desc": "Lista CIDR-ów, adresów IP lub identyfikatorów klientów. Jeśli zostanie skonfigurowana, AdGuard Home będzie przyjmował żądania tylko od tych klientów.", + "access_allowed_desc": "Lista identyfikatorów CIDR, adresów IP lub identyfikatorów klienta. Jeśli ta lista zawiera wpisy, AdGuard Home zaakceptuje żądania tylko od tych klientów.", "access_disallowed_title": "Niedozwoleni klienci", - "access_disallowed_desc": "Lista CIDR-ów, adresów IP lub identyfikatorów klientów. Jeśli jest skonfigurowana, AdGuard Home będzie odrzucał żądania od tych klientów. Jeśli skonfigurowano dozwolonych klientów, pole to jest ignorowane.", + "access_disallowed_desc": "Lista identyfikatorów CIDR, adresów IP lub identyfikatorów klienta. Jeśli ta lista zawiera wpisy, AdGuard Home odrzuci żądania od tych klientów. To pole jest ignorowane, jeśli istnieją wpisy w Dozwolonych klientach.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dni", "interval_days_plural": "{{count}} dni", "domain": "Domena", + "ecs": "ECS", "punycode": "Punycode", "answer": "Odpowiedź", "filter_added_successfully": "Lista została pomyślnie dodana", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} godziny", "filters_configuration": "Konfiguracja filtrów", "filters_enable": "Włącz filtry", - "filters_interval": "Częstotliwość aktualizacji filtrów", + "filters_interval": "Aktualizuj filtry co", "disabled": "Wyłączone", "username_label": "Nazwa użytkownika", "username_placeholder": "Wpisz nazwę użytkownika", @@ -532,7 +537,7 @@ "netname": "Nazwa sieci", "network": "Sieć", "descr": "Opis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Dowiedz się więcej o tworzeniu własnych list blokowania hostów.", "blocked_by_response": "W odpowiedzi zablokowany przez CNAME lub IP", "blocked_by_cname_or_ip": "Zablokowany przez rekord CNAME lub adres IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Wykona następujące zadania: <0>Dezaktywuj system DNSStubListener <0>Ustaw adres serwera DNS na 127.0.0.1 <0>Zamień symboliczny cel łącza z /etc/resolv.conf na /run/systemd/resolve/resolv.conf <0>Zatrzymaj DNSStubListener (przeładuj usługę systemową)", "autofix_warning_result": "W rezultacie wszystkie żądania DNS z Twojego systemu będą domyślnie przetwarzane przez AdGuardHome.", "tags_title": "Tagi", - "tags_desc": "Możesz wybrać tagi odpowiadające klientowi. Tagi mogą być uwzględnione w regułach filtrowania i umożliwiają ich dokładniejsze stosowanie. <0>Dowiedz się więcej ", + "tags_desc": "Możesz wybrać tagi, które odpowiadają klientowi. Uwzględnij tagi w regułach filtrowania, aby zastosować je dokładniej. <0>Dowiedz się więcej.", "form_select_tags": "Wybierz tagi klienta", "check_title": "Sprawdź filtrowanie", - "check_desc": "Sprawdź, czy nazwa hosta jest filtrowana", + "check_desc": "Sprawdź, czy nazwa hosta jest filtrowana.", "check": "Sprawdź", "form_enter_host": "Wpisz nazwę hosta", "filtered_custom_rules": "Filtrowane według niestandardowych reguł filtrowania", @@ -598,15 +603,15 @@ "blocklist": "Lista zablokowanych", "milliseconds_abbreviation": "ms", "cache_size": "Rozmiar pamięci podręcznej", - "cache_size_desc": "Rozmiar pamięci podręcznej DNS (w bajtach)", + "cache_size_desc": "Rozmiar pamięci podręcznej DNS (w bajtach).", "cache_ttl_min_override": "Nadpisz minimalną wartość TTL", "cache_ttl_max_override": "Nadpisz maksymalną wartość TTL", "enter_cache_size": "Wpisz rozmiar pamięci podręcznej (w bajtach)", "enter_cache_ttl_min_override": "Wpisz minimalną wartość TTL (w sekundach)", "enter_cache_ttl_max_override": "Wpisz maksymalną wartość TTL (w sekundach)", - "cache_ttl_min_override_desc": "Zastąp wartość TTL (w sekundach) otrzymaną z serwera nadrzędnego podczas buforowania odpowiedzi DNS", - "cache_ttl_max_override_desc": "Ustaw maksymalną wartość TTL (w sekundach) dla wpisów w pamięci podręcznej DNS", - "ttl_cache_validation": "Minimalna pamięć podręczna wartości TTL musi być mniejsza lub równa maksymalnej wartości", + "cache_ttl_min_override_desc": "Przedłuż najkrótszą wartość TTL (w sekundach) otrzymaną od serwera wychodzącego podczas buforowania odpowiedzi DNS.", + "cache_ttl_max_override_desc": "Ustaw maksymalną wartość czasu życia (w sekundach) dla wpisów w pamięci podręcznej DNS.", + "ttl_cache_validation": "Minimalne nadpisanie pamięci podręcznej TTL musi być mniejsze lub równe maksimum", "cache_optimistic": "Optymistyczne buforowanie", "cache_optimistic_desc": "Spraw, aby AdGuard Home odpowiadał z pamięci podręcznej, nawet gdy wpisy wygasły, a także spróbuj je odświeżyć.", "filter_category_general": "Ogólne", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home odrzuci zapytanie DNS od tego klienta.", "filter_allowlist": "OSTRZEŻENIE: To działanie spowoduje również wykluczenie reguły \"{{disallowed_rule}}\" z listy dozwolonych klientów.", "last_rule_in_allowlist": "Nie można odrzucić tego klienta, ponieważ wykluczenie reguły \"{{disallowed_rule}}\" spowoduje WYŁĄCZENIE listy „Dozwolonych klientów”.", - "experimental": "Funkcja eksperymentalna", "use_saved_key": "Użyj wcześniej zapisanego klucza", "parental_control": "Kontrola rodzicielska", "safe_browsing": "Bezpieczne przeglądanie", - "served_from_cache": "{{value}} (podawane z pamięci podręcznej)" + "served_from_cache": "{{value}} (podawane z pamięci podręcznej)", + "form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków" } diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index c71e619a..dce24636 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -1,7 +1,7 @@ { "client_settings": "Configurações do cliente", - "example_upstream_reserved": "Você pode especificar o DNS primário <0>para o domínio(s) especifico", - "example_upstream_comment": "Você pode especificar o comentário", + "example_upstream_reserved": "um DNS primário <0>para o domínios especificos;", + "example_upstream_comment": "um comentário.", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos oss servidores DNS primário", "parallel_requests": "Solicitações paralelas", "load_balancing": "Balanceamento de carga", @@ -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", @@ -38,7 +38,7 @@ "form_error_required": "Campo obrigatório", "form_error_ip4_format": "Endereço de IPv4 inválido", "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido", - "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido", + "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido.", "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido", "form_error_ip6_format": "Endereço de IPv6 inválido", "form_error_ip_format": "Endereço de IP inválido", @@ -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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nome do servidor", "dhcp_table_expires": "Expira", "dhcp_warning": "Se você quiser ativar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP ativo em sua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!", - "dhcp_error": "O AdGuard Home não conseguiu determinar se há outro servidor DHCP ativo na rede.", + "dhcp_error": "O AdGuard Home não conseguiu determinar se há outro servidor DHCP ativo na rede", "dhcp_static_ip_error": "Para usar o servidor DHCP, você deve definir um endereço IP estático. AdGuard Home não conseguiu determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.", "dhcp_dynamic_ip_found": "Seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}. Para usar o servidor DHCP, você deve definir um endereço de IP estático. Seu endereço IP atual é <0> {{ipAddress}} . AdGuard Home irá definir automaticamente este endereço IP como estático se você pressionar o botão \"Ativar servidor DHCP\".", "dhcp_lease_added": "Concessão estática \"{{key}}\" adicionada com sucesso", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Digite uma regra por linha. Você pode usar regras de bloqueio de anúncios ou a sintaxe de arquivos de hosts.", "system_host_files": "Arquivos hosts do sistema", "examples_title": "Exemplos", - "example_meaning_filter_block": "bloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios", - "example_meaning_filter_whitelist": "desbloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios", - "example_meaning_host_block": "O AdGuard Home irá retornar o endereço 127.0.0.1 para o domínio exemplo.org (exceto seus subdomínios).", - "example_comment": "! Aqui vai um comentário", - "example_comment_meaning": "apenas um comentário", - "example_comment_hash": "# Também um comentário", - "example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à expressão regular especificada", - "example_upstream_regular": "DNS regular (através do UDP)", - "example_upstream_dot": "<0>DNS-sobre-TLS criptografado", - "example_upstream_doh": "<0>DNS-sobre-HTTPS criptografado", - "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado", - "example_upstream_sdns": "você pode usar <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_meaning_filter_block": "bloqueia o acesso ao exemplo.org e a todos os seus subdomínios;", + "example_meaning_filter_whitelist": "desbloqueia o acesso ao exemplo.org e a todos os seus subdomínios;", + "example_meaning_host_block": "responde o endereço 127.0.0.1 para o exemplo.org (exceto seus subdomínios);", + "example_comment": "! Aqui vai um comentário.", + "example_comment_meaning": "apenas um comentário;", + "example_comment_hash": "# Também um comentário.", + "example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à expressão regular especificada.", + "example_upstream_regular": "dNS regular (através do UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas", "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", @@ -259,7 +263,7 @@ "query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa", "query_log_retention_confirm": "Você tem certeza de que deseja alterar o arquivamento do registro de consulta? Se diminuir o valor de intervalo, alguns dados serão perdidos", "anonymize_client_ip": "Tornar anônimo o IP do cliente", - "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros e estatísticas", + "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas", "dns_config": "Configuração do servidor DNS", "dns_cache_config": "Configuração de cache DNS", "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-sobre-QUIC", "client_id": "ID do cliente", "client_id_placeholder": "Digite o ID do cliente", - "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. Aqui você pode aprender mais sobre como identificar clientes.", + "client_id_desc": "Os clientes podem ser identificados por um ID de cliente especial. Saiba mais como identificar clientes aqui.", "download_mobileconfig_doh": "BAixar .mobileconfig para DNS-sobre-HTTPS", "download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS", "download_mobileconfig": "Baixar arquivo de configuração", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Insira a taxa limite", "rate_limit": "Taxa limite", "edns_enable": "Ativar a sub-rede do cliente EDNS", - "edns_cs_desc": "Envia as sub-redes dos clientes para os servidores DNS.", + "edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registro de consulta.", "rate_limit_desc": "O número de solicitações por segundo permitidas por cliente. Definir como 0 significa que não há limite.", "blocking_ipv4_desc": "Endereço de IP a ser retornado para uma solicitação bloqueada", "blocking_ipv6_desc": "Endereço de IP a ser retornado para uma solicitação AAAA bloqueada", @@ -334,11 +338,11 @@ "install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP pode ajudar. Caso contrário, você deve verificar o manual do roteador sobre como personalizar os servidores DNS em seu modelo de roteador específico.", "install_devices_windows_list_1": "Abra o Painel de Controle pelo Menu Iniciar ou pela Pesquisa do Windows.", "install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Compartilhamento.", - "install_devices_windows_list_3": "No lado esquerdo da janela clique em \"Alterar as configurações do adaptador\".", - "install_devices_windows_list_4": "Selecione sua atual conexão, clique nela com o botão direito do mouse e depois clique em Propriedades.", + "install_devices_windows_list_3": "No painel esquerdo, clique em \"Alterar configurações do adaptador\".", + "install_devices_windows_list_4": "Clique com o botão direito do mouse em sua conexão ativa e selecione Propriedades.", "install_devices_windows_list_5": "Procure na lista por \"Internet Protocol Version 4 (TCP/IP)\" (ou por IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), selecione e clique em Propriedades novamente.", "install_devices_windows_list_6": "Marque \"usar os seguintes endereços de servidor DNS\" e digite os endereços do servidores do AdGuard Home.", - "install_devices_macos_list_1": "Clique na ícone da Apple e depois em Preferências do Sistema.", + "install_devices_macos_list_1": "Clique no ícone da Apple e depois em Preferências do Sistema.", "install_devices_macos_list_2": "Clique em Rede.", "install_devices_macos_list_3": "Selecione a primeira conexão da lista e clique em Avançado.", "install_devices_macos_list_4": "Selecione a guia DNS e digite os endereços dos servidores do AdGuard Home.", @@ -356,11 +360,11 @@ "open_dashboard": "Abrir painel", "install_saved": "Salvo com sucesso", "encryption_title": "Criptografia", - "encryption_desc": "Suporte a criptografia (HTTPS/TLS) para DNS e interface de administração web", + "encryption_desc": "Suporte a criptografia (HTTPS/QUIC/TLS) para DNS e interface de administração web", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -378,7 +382,7 @@ "encryption_key_input": "Copie/cole aqui a chave privada codificada em PEM para seu certificado.", "encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)", "encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.", - "encryption_chain_valid": "Cadeia de chave válida.", + "encryption_chain_valid": "Cadeia de chave válida", "encryption_chain_invalid": "A cadeia de certificado é inválida", "encryption_key_valid": "Esta é uma chave privada {{type}} válida", "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia", "topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia", "form_error_port_range": "Digite um número de porta entre 80 e 65535", - "form_error_port_unsafe": "Esta porta não é segura", + "form_error_port_unsafe": "Porta não é segura", "form_error_equal": "Não deve ser igual", "form_error_password": "Senhas não coincidem", "reset_settings": "Redefinir configurações", @@ -397,7 +401,7 @@ "setup_guide": "Guia de configuração", "dns_addresses": "Endereços DNS", "dns_start": "O servidor DNS está iniciando", - "dns_status_error": "Ocorreu um erro ao obter o status do servidor DNS", + "dns_status_error": "Ocorreu um erro ao verificar o status do servidor DNS", "down": "Caiu", "fix": "Corrigido", "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos para escolher.", @@ -405,8 +409,8 @@ "update_failed": "A atualização automática falhou. Por favor, siga estes passos para atualizar manualmente.", "manual_update": "Por favor, siga estes passos para atualizar manualmente.", "processing_update": "Por favor, aguarde enquanto o AdGuard Home está sendo atualizado", - "clients_title": "Clientes", - "clients_desc": "Configure dispositivos conectados ao AdGuard", + "clients_title": "Clientes persistentes", + "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home", "settings_global": "Global", "settings_custom": "Personalizado", "table_client": "Cliente", @@ -417,7 +421,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Endereço de IP", - "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui você pode aprender mais sobre como identificar clientes.", + "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). Saiba mais sobre como identificar clientes <0>aqui.", "form_enter_ip": "Digite o endereço de IP", "form_enter_subnet_ip": "Digite um endereço IP na sub-rede \"{{cidr}}\"", "form_enter_mac": "Digite o endereço MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Nenhum cliente foi encontrado", "client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", - "auto_clients_title": "Clientes (tempo de execução)", - "auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na configuração", + "auto_clients_title": "Clientes ativos", + "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home", "access_title": "Configurações de acessos", - "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.", + "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_allowed_title": "Clientes permitidos", - "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home do aceitará solicitações apenas desses clientes.", + "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.", "access_disallowed_title": "Clientes não permitidos", - "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home descartará as solicitações desses clientes. Se clientes permitidos estiverem configurados, este campo será ignorado.", + "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se essa lista tiver entradas, o AdGuard Home descartará as solicitações desses clientes. Este campo é ignorado se houver entradas em clientes permitidos.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dias", "interval_days_plural": "{{count}} dias", "domain": "Domínio", + "ecs": "ECS", "punycode": "Punycode", "answer": "Resposta", "filter_added_successfully": "O filtro foi adicionado com sucesso", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} horas", "filters_configuration": "Configuração de filtros", "filters_enable": "Ativar filtros", - "filters_interval": "Intervalo de atualização de filtros", + "filters_interval": "Intervalo de atualização de filtro", "disabled": "Desativado", "username_label": "Nome de usuário", "username_placeholder": "Digite o nome de usuário", @@ -532,7 +537,7 @@ "netname": "Nome da rede", "network": "Rede", "descr": "Descrição", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Saiba mais sobre como criar as suas próprias listas negras de servidores.", "blocked_by_response": "Bloqueado por CNAME ou IP na resposta", "blocked_by_cname_or_ip": "Bloqueado por CNAME ou IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Ele irá realizar estas tarefas: <0>Desativar sistema DNSStubListener <0>Definir endereço do servidor DNS para 127.0.0.1 <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)", "autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por padrão.", "tags_title": "Marcadores", - "tags_desc": "Você pode selecionar as tags que correspondem ao cliente. As tags podem ser incluídas nas regras de filtragem e permitir que você as aplique com mais precisão. <0>Saiba mais", + "tags_desc": "Você pode selecionar tags que correspondam ao cliente. Inclua tags nas regras de filtragem para aplicá-las com mais precisão. <0>Saber mais.", "form_select_tags": "Selecione as tags do cliente", "check_title": "Verifique a filtragem", - "check_desc": "Verificar se o nome do host está sendo filtrado", + "check_desc": "Verificar se um nome do host está sendo filtrado.", "check": "Verificar", "form_enter_host": "Digite o nome do host", "filtered_custom_rules": "Filtrado pelas regras de filtragem personalizadas", @@ -598,15 +603,15 @@ "blocklist": "Lista de bloqueio", "milliseconds_abbreviation": "ms", "cache_size": "Tamanho do cache", - "cache_size_desc": "Tamanho do cache do DNS (em bytes)", + "cache_size_desc": "Tamanho do cache do DNS (em bytes).", "cache_ttl_min_override": "Sobrepor o TTL mínimo", "cache_ttl_max_override": "Sobrepor o TTL máximo", "enter_cache_size": "Digite o tamanho do cache (bytes)", "enter_cache_ttl_min_override": "Digite o TTL máximo (segundos)", "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)", - "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS", - "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS", - "ttl_cache_validation": "O valor TTL mínimo do cache deve ser menor ou igual ao valor máximo", + "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.", + "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.", + "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo", "cache_optimistic": "Cache otimista", "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.", "filter_category_general": "Geral", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.", "filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.", "last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".", - "experimental": "Experimental", "use_saved_key": "Use a chave salva anteriormente", "parental_control": "Controle parental", "safe_browsing": "Navegação segura", - "served_from_cache": "{{value}} (servido do cache)" + "served_from_cache": "{{value}} (servido do cache)", + "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres" } diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index 0405f5c1..6003952e 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -1,7 +1,7 @@ { "client_settings": "Definições do cliente", "example_upstream_reserved": "Podes especificar o DNS primário <0>para domínio(s) especifico(s)", - "example_upstream_comment": "Tu podes especificar o comentário", + "example_upstream_comment": "um comentário.", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS", "parallel_requests": "Solicitações paralelas", "load_balancing": "Balanceamento de carga", @@ -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", @@ -41,12 +41,13 @@ "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido", "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido", "form_error_ip6_format": "Endereço de IPv6 inválido", - "form_error_ip_format": "Endereço de IP inválido", + "form_error_ip_format": "Endereço de email inválido", "form_error_mac_format": "Endereço de MAC inválido", "form_error_client_id_format": "O ID do cliente deve conter apenas números, letras minúsculas e hifens", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Nome do servidor", "dhcp_table_expires": "Expira", "dhcp_warning": "Se tu quiser ativar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP ativo em tua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!", - "dhcp_error": "O AdGuard Home não conseguiu determinar se há noutro servidor DHCP ativo na rede.", + "dhcp_error": "O AdGuard Home não conseguiu determinar se há noutro servidor DHCP ativo na rede", "dhcp_static_ip_error": "Para usar o servidor DHCP, deve definir um endereço IP estático. AdGuard Home não conseguiu determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.", "dhcp_dynamic_ip_found": "O seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}. Para usar o servidor DHCP, deve definir um endereço de IP estático. O seu endereço IP atual é <0> {{ipAddress}} . AdGuard Home irá definir automaticamente este endereço IP como estático se pressionar o botão \"Ativar servidor DHCP\".", "dhcp_lease_added": "Concessão estática \"{{key}}\" adicionada com sucesso", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Insira uma regra por linha. Pode usar regras de bloqueio de anúncios ou a sintaxe de ficheiros de hosts.", "system_host_files": "Arquivos hosts do sistema", "examples_title": "Exemplos", - "example_meaning_filter_block": "bloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios", - "example_meaning_filter_whitelist": "desbloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios", - "example_meaning_host_block": "O AdGuard Home irá retornar o endereço 127.0.0.1 para o domínio exemplo.org (excepto os seus subdomínios).", - "example_comment": "! Aqui vai um comentário", - "example_comment_meaning": "apenas um comentário", - "example_comment_hash": "# Também um comentário", - "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada", + "example_meaning_filter_block": "bloqueia o acesso ao exemplo.org e a todos os seus subdomínios;", + "example_meaning_filter_whitelist": "desbloqueia o acesso ao exemplo.org e a todos os seus subdomínios;", + "example_meaning_host_block": "retorna o endereço 127.0.0.1 para o exemplo.org (exceto seus subdomínios);", + "example_comment": "! Aqui vai um comentário.", + "example_comment_meaning": "apenas um comentário;", + "example_comment_hash": "# Também um comentário.", + "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada.", "example_upstream_regular": "DNS regular (através do UDP)", - "example_upstream_dot": "<0>DNS-sobre-TLS criptografado", - "example_upstream_doh": "<0>DNS-sobre-HTTPS criptografado", - "example_upstream_doq": "<0>DNS-sobre-QUIC criptografado", - "example_upstream_sdns": "pode usar <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_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;", + "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);", "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas", "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", @@ -259,7 +263,7 @@ "query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa", "query_log_retention_confirm": "Tem a certeza de que deseja alterar a retenção do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos", "anonymize_client_ip": "Tornar anónimo o IP do cliente", - "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros e estatísticas", + "anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registros ou estatísticas", "dns_config": "Definição do servidor DNS", "dns_cache_config": "Definição de cache DNS", "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-sobre-QUIC", "client_id": "ID do cliente", "client_id_placeholder": "Insira o ID do cliente", - "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. Aqui você pode aprender mais sobre como identificar clientes.", + "client_id_desc": "Os clientes podem ser identificados por um ID de cliente especial. Saiba mais como identificar clientes aqui.", "download_mobileconfig_doh": "Transferir .mobileconfig para DNS-sobre-HTTPS", "download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS", "download_mobileconfig": "Transferir ficheiro de configuração", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Insira o limite de taxa", "rate_limit": "Limite de taxa", "edns_enable": "Ativar a sub-rede do cliente EDNS", - "edns_cs_desc": "Envia as sub-redes dos clientes para os servidores DNS.", + "edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registro de consulta.", "rate_limit_desc": "O número de solicitações por segundo permitido por cliente. Configurando para 0 significa sem limite.", "blocking_ipv4_desc": "Endereço IP a ser devolvido para uma solicitação A bloqueada", "blocking_ipv6_desc": "Endereço IP a ser devolvido para uma solicitação AAAA bloqueada", @@ -334,11 +338,11 @@ "install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP pode ajudar. Caso contrário, tu deve verificar o manual do router sobre como personalizar os servidores DNS em seu modelo de router específico.", "install_devices_windows_list_1": "Abra o Painel de Controlo através do Menu Iniciar ou pela Pesquisa do Windows.", "install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Partilha.", - "install_devices_windows_list_3": "No lado esquerdo da janela clique em \"Alterar as definições do adaptador\".", - "install_devices_windows_list_4": "Selecione sua atual ligação, clique nela com o botão direito do rato e depois clique em Propriedades.", + "install_devices_windows_list_3": "No painel esquerdo, clique em \"Alterar configurações do adaptador\".", + "install_devices_windows_list_4": "Clique com o botão direito do mouse em sua conexão ativa e selecione Propriedades.", "install_devices_windows_list_5": "Procure na lista por \"Internet Protocol Version 4 (TCP/IP)\" (ou por IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), selecione e clique em Propriedades novamente.", "install_devices_windows_list_6": "Marque \"Usar os seguintes endereços de servidor DNS\" e insira os endereços do servidores do AdGuard Home.", - "install_devices_macos_list_1": "Clique na ícone da Apple e depois em Preferências do Sistema.", + "install_devices_macos_list_1": "Clique no ícone da Apple e depois em Preferências do Sistema.", "install_devices_macos_list_2": "Clique em Rede.", "install_devices_macos_list_3": "Selecione a primeira ligação da lista e clique em Avançado.", "install_devices_macos_list_4": "Selecione a guia DNS e insira os endereços dos servidores do AdGuard Home.", @@ -356,11 +360,11 @@ "open_dashboard": "Abrir Painel", "install_saved": "Guardado com sucesso", "encryption_title": "Encriptação", - "encryption_desc": "Suporta a criptografia (HTTPS/TLS) para DNS e interface de administração web", + "encryption_desc": "Suporta a criptografia (HTTPS/QUIC/TLS) para DNS e interface de administração web", "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia.", "topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia.", "form_error_port_range": "Digite um numero de porta entre 80 e 65535", - "form_error_port_unsafe": "Esta porta não é segura", + "form_error_port_unsafe": "Porta não é segura", "form_error_equal": "Não deve ser igual", "form_error_password": "As palavras-passe não coincidem", "reset_settings": "Repor definições", @@ -397,7 +401,7 @@ "setup_guide": "Guia de instalação", "dns_addresses": "Endereços DNS", "dns_start": "O servidor DNS está a iniciar", - "dns_status_error": "Erro ao obter o estado do servidor DNS", + "dns_status_error": "Ocorreu um erro ao verificar o estado do servidor DNS", "down": "Caiu", "fix": "Corrigido", "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos para escolher.", @@ -405,8 +409,8 @@ "update_failed": "A atualização automática falhou. Por favor, siga estes passos para atualizar manualmente.", "manual_update": "Por favor, siga estes passos para atualizar manualmente.", "processing_update": "Por favor espere, o AdGuard Home está a atualizar-se", - "clients_title": "Clientes", - "clients_desc": "Configure os dispositivos ligados ao AdGuard", + "clients_title": "Clientes persistentes", + "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home", "settings_global": "Global", "settings_custom": "Personalizar", "table_client": "Cliente", @@ -417,7 +421,7 @@ "client_edit": "Editar cliente", "client_identifier": "Identificador", "ip_address": "Endereço de IP", - "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui você pode aprender mais sobre como identificar clientes.", + "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). Saiba mais sobre como identificar clientes <0>aqui.", "form_enter_ip": "Insira IP", "form_enter_subnet_ip": "Digite um endereço IP na sub-rede \"{{cidr}}\"", "form_enter_mac": "Insira o endereço MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Nenhum cliente foi encontrado", "client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", - "auto_clients_title": "Clientes (tempo de execução)", - "auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na definição", + "auto_clients_title": "Clientes ativos", + "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home", "access_title": "Definições de acesso", - "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.", + "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_allowed_title": "Clientes permitidos", - "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home do aceitará solicitações apenas desses clientes.", + "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.", "access_disallowed_title": "Clientes não permitidos", - "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home descartará as solicitações desses clientes. Se clientes permitidos estiverem configurados, este campo será ignorado.", + "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se essa lista tiver entradas, o AdGuard Home descartará as solicitações desses clientes. Este campo é ignorado se houver entradas em clientes permitidos.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dias", "interval_days_plural": "{{count}} dias", "domain": "Domínio", + "ecs": "ECS", "punycode": "Punycode", "answer": "Resposta", "filter_added_successfully": "O filtro foi adicionado com sucesso", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} horas", "filters_configuration": "Definição dos filtros", "filters_enable": "Ativar filtros", - "filters_interval": "Intervalo de atualização de filtros", + "filters_interval": "Intervalo de atualização de filtro", "disabled": "Desativado", "username_label": "Nome do utilizador", "username_placeholder": "Insira o nome de utilizador", @@ -532,7 +537,7 @@ "netname": "Nome da rede", "network": "Rede", "descr": "Descrição", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Saiba maissobre como criar as suas próprias listas negras de servidores.", "blocked_by_response": "Bloqueado por CNAME ou IP em resposta", "blocked_by_cname_or_ip": "Bloqueado por CNAME ou IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Irá realizar estas tarefas: <0>Desativar sistema DNSStubListener <0>Definir endereço do servidor DNS para 127.0.0.1 <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)", "autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por predefinição.", "tags_title": "Etiquetas", - "tags_desc": "Tu podes selecionar as etiquetas que correspondem ao cliente. As etiquetas podem ser incluídas nas regras de filtragem e permitir que tu as aplique com mais precisão. <0>Saiba mais", + "tags_desc": "Você pode selecionar tags que correspondam ao cliente. Inclua tags nas regras de filtragem para aplicá-las com mais precisão. <0>Saber mais.", "form_select_tags": "Selecione as tags do cliente", "check_title": "Verifique a filtragem", - "check_desc": "Verificar se o nome do host está sendo filtrado", + "check_desc": "Verificar se um nome do host está sendo filtrado.", "check": "Verificar", "form_enter_host": "Insira o hostname", "filtered_custom_rules": "Filtrado pelas regras de filtragem personalizadas", @@ -598,15 +603,15 @@ "blocklist": "Lista de bloqueio", "milliseconds_abbreviation": "ms", "cache_size": "Tamanho do cache", - "cache_size_desc": "Tamanho do cache do DNS (em bytes)", + "cache_size_desc": "Tamanho do cache do DNS (em bytes).", "cache_ttl_min_override": "Sobrepor o TTL mínimo", "cache_ttl_max_override": "Sobrepor o TTL máximo", "enter_cache_size": "Digite o tamanho do cache (bytes)", "enter_cache_ttl_min_override": "Digite o TTL máximo (segundos)", "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)", - "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS", - "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS", - "ttl_cache_validation": "O valor TTL mínimo do cache deve ser menor ou igual ao valor máximo", + "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.", + "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.", + "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo", "cache_optimistic": "Cache otimista", "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.", "filter_category_general": "Geral", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.", "filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.", "last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".", - "experimental": "Experimental", "use_saved_key": "Use a chave guardada anteriormente", "parental_control": "Controlo parental", "safe_browsing": "Navegação segura", - "served_from_cache": "{{value}} (servido do cache)" + "served_from_cache": "{{value}} (servido do cache)", + "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres" } diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 0e959ee7..e0bf4fd0 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -1,21 +1,21 @@ { "client_settings": "Setări client", - "example_upstream_reserved": "Puteți specifica un DNS în amonte <0>pentru domeniul (domeniile) specific(e)", - "example_upstream_comment": "Puteți specifica un comentariu", + "example_upstream_reserved": "un flux în amonte <0>pentru domenii specifice;", + "example_upstream_comment": "un comentariu.", "upstream_parallel": "Folosiți interogări paralele pentru a accelera rezolvarea, interogând simultan toate serverele în amonte.", "parallel_requests": "Solicitări paralele", "load_balancing": "Echilibrare-sarcini", "load_balancing_desc": "Interoghează câte un server în amonte la un moment dat. AdGuard Home utilizează un algoritm de randomizare ponderat pentru a alege serverul, astfel încât cel mai rapid server să fie utilizat mai des.", "bootstrap_dns": "Serverele DNS Bootstrap", - "bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale resolverelor DoH/DoT indicate ca upstreams.", + "bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale rezolvatorilor DoH/DoT indicați ca upstreams.", "local_ptr_title": "Servere DNS inverse private", - "local_ptr_desc": "Servere DNS pe care AdGuard Home le utilizează pentru interogări PTR locale. Aceste servere sunt folosite pentru a rezolva numele gazdelor de clienți cu adrese IP private, cum ar fi \"192.168.12.34\", folosind DNS inversat. Dacă nu este setat, AdGuard Home utilizează adresele resolverelor DNS implicite ale SO al dvs., cu excepția adreselor AdGuard Home înseși.", - "local_ptr_default_resolver": "În mod implicit, AdGuard Home utilizează următoarele resolvere DNS inverse: {{ip}}.", - "local_ptr_no_default_resolver": "AdGuard Home nu a putut determina resolvere DNS private adecvate pentru acest sistem.", + "local_ptr_desc": "Serverele DNS pe care AdGuard Home le utilizează pentru interogările PTR locale. Aceste servere sunt utilizate pentru a rezolva solicitările PTR pentru adrese din intervale IP private, de exemplu „192.168.12.34”, utilizând DNS invers. Dacă nu este configurat, AdGuard Home utilizează adresele rezolvatorilor DNS impliciți ai sistemului dvs. de operare, cu excepția adreselor AdGuard Home în sine.", + "local_ptr_default_resolver": "În mod implicit, AdGuard Home utilizează următorii rezolvatori DNS inverși: {{ip}}.", + "local_ptr_no_default_resolver": "AdGuard Home nu a putut determina rezolvatorii DNS privați adecvați pentru acest sistem.", "local_ptr_placeholder": "Introduceți o adresă de server per linie", "resolve_clients_title": "Permiteți rezolvarea inversa a adreselor IP ale clienților", - "resolve_clients_desc": "Rezolvă invers adresele IP ale clienților în numele lor de gazde prin trimiterea interogărilor PTR la resolverele corespunzătoare (servere DNS private pentru clienți locali, servere în amonte pentru clienți cu adrese IP publice).", - "use_private_ptr_resolvers_title": "Utilizați resolvere DNS inverse private", + "resolve_clients_desc": "Rezolvă invers adresele IP ale clienților în numele lor de gazde prin trimiterea interogărilor PTR la rezolvatorii corespunzători (servere DNS private pentru clienți locali, servere în amonte pentru clienți cu adrese IP publice).", + "use_private_ptr_resolvers_title": "Utilizați rezolvatori DNS inverși privați", "use_private_ptr_resolvers_desc": "Efectuează examinări DNS inverse pentru adresele deservite local folosind aceste servere în amonte. Dacă este dezactivată, AdGuard Home răspunde cu NXDOMAIN la toate aceste cereri PTR, cu excepția clienților cunoscuți din DHCP, /etc/hosts și așa mai departe.", "check_dhcp_servers": "Căutați servere DHCP", "save_config": "Salvare configurare", @@ -35,18 +35,19 @@ "dhcp_config_saved": "Configurare DHCP salvată cu succes", "dhcp_ipv4_settings": "Setări DHCP IPv4", "dhcp_ipv6_settings": "Setări DHCP IPv6", - "form_error_required": "Câmp necesar", + "form_error_required": "Câmp obligatoriu", "form_error_ip4_format": "Adresă IPv4 nevalidă", - "form_error_ip4_range_start_format": "Adresă IPv4 de început al intervalului nevalidă", - "form_error_ip4_range_end_format": "Adresa IPv4 de sfârșit al intervalului nevalidă", - "form_error_ip4_gateway_format": "Adresă IPv4 a gateway-ului nevalidă", + "form_error_ip4_range_start_format": "Adresă IPv4 nevalidă pentru începutul intervalului", + "form_error_ip4_range_end_format": "Adresă IPv4 nevalidă a sfârșitului intervalului", + "form_error_ip4_gateway_format": "Adresă IPv4 nevalidă a gateway-ului", "form_error_ip6_format": "Adresa IPv6 nevalidă", "form_error_ip_format": "Adresă IP nevalidă", "form_error_mac_format": "Adresă MAC nevalidă", - "form_error_client_id_format": "ID-ul clientului trebuie să conțină numai numere, litere minuscule și liniuțe.", + "form_error_client_id_format": "ClientID-ul trebuie să conțină numai numere, litere minuscule și cratime", "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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Hostname", "dhcp_table_expires": "Expiră", "dhcp_warning": "Dacă doriți să activați serverul DHCP oricum, asigurați-vă că nu există nici un alt server DHCP activ în rețeaua dvs., deoarece acest lucru poate rupe conectivitatea la Internet a dispozitivelor din rețea!", - "dhcp_error": "AdGuard Home nu a putut determina dacă există un alt server DHCP activ în rețea.", + "dhcp_error": "AdGuard Home nu a putut determina dacă există un alt server DHCP activ în rețea", "dhcp_static_ip_error": "Pentru a utiliza serverul DHCP, trebuie setată o adresă IP statică. AdGuard Home nu a reușit să determine dacă această interfață de rețea este configurată utilizând o adresă IP statică. Setați manual o adresă IP statică.", "dhcp_dynamic_ip_found": "Sistemul dvs. folosește configurația dinamică a adreselor IP pentru interfața <0>{{interfaceName}}. Pentru a utiliza serverul DHCP, trebuie setată o adresă IP statică. Adresa IP curentă este <0>{{ipAddress}}. AdGuard Home o va configura automat ca adresă IP statică, dacă apăsați butonul \"Activați serverul DHCP\".", "dhcp_lease_added": "\"{{key}}\" statică închiriată adăugată cu succes", @@ -196,29 +197,32 @@ "choose_allowlist": "Selectați liste de autorizări", "enter_valid_blocklist": "Introduceți un URL valid pentru blocare.", "enter_valid_allowlist": "Introduceți un URL valid pentru autorizare.", - "form_error_url_format": "Format URL invalid", - "form_error_url_or_path_format": "Invalid URL sau o cale absolută a listei", + "form_error_url_format": "Format URL nevalid", + "form_error_url_or_path_format": "URL nevalabil sau calea absolută a listei", "custom_filter_rules": "Reguli de filtrare personalizate", "custom_filter_rules_hint": "Introduceți o regulă pe linie. Puteți utiliza reguli de blocare sau sintaxa de fișiere hosts.", "system_host_files": "Fișiere de sistem hosts", "examples_title": "Exemple", - "example_meaning_filter_block": "blochează accesul la domeniul exemplu.org și la toate subdomeniile sale", - "example_meaning_filter_whitelist": "deblochează accesul la domeniul exemplu.org și la toate subdomeniile sale", - "example_meaning_host_block": "AdGuard Home va returna acum adresa 127.0.0.1 pentru domeniul exemplu.org (dar nu și subdomeniile sale).", - "example_comment": "! Iată cum se adăugă o descriere", - "example_comment_meaning": "comentariu", - "example_comment_hash": "# Astfel putem lăsa comentarii", - "example_regex_meaning": "blocare acces la domenii care corespund expresiei obișnuite specificate", - "example_upstream_regular": "DNS clasic (over UDP)", - "example_upstream_dot": "<0>DNS-over-TLS criptat", - "example_upstream_doh": "<0>DNS-over-HTTPS criptat", - "example_upstream_doq": "<0>DNS-over-QUIC criptat", - "example_upstream_sdns": "puteți utiliza <0>DNS Stamps pentru rezolvere <1>DNSCrypt sau <2>DNS-over-HTTPS", - "example_upstream_tcp": "DNS clasic (over TCP)", + "example_meaning_filter_block": "blochează accesul la domeniul exemplu.org și la toate subdomeniile sale;", + "example_meaning_filter_whitelist": "deblochează accesul la domeniul exemplu.org și la toate subdomeniile sale;", + "example_meaning_host_block": "răspunde cu 127.0.0.1 pentru domeniul exemplu.org (dar nu și pentru subdomeniile sale);", + "example_comment": "! Aici urmează un comentariu.", + "example_comment_meaning": "doar un comentariu;", + "example_comment_hash": "# De asemenea, un comentariu.", + "example_regex_meaning": "blochează accesul la domeniile care corespund expresiei regulate specificate.", + "example_upstream_regular": "DNS clasic (over UDP);", + "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": "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ă);", "all_lists_up_to_date_toast": "Toate listele sunt deja la zi", "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", @@ -275,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID Client", - "client_id_placeholder": "Introduceți ID client", - "client_id_desc": "Diferiți clienți pot fi identificați printr-un ID special al clientului. Aici puteți afla mai multe despre cum să identificați clienții.", + "client_id": "ClientID", + "client_id_placeholder": "Introduceți un ClientID", + "client_id_desc": "Clienții pot fi identificați prin ClientID. Aflați mai multe despre cum să identificați clienții aici.", "download_mobileconfig_doh": "Descărcați .mobileconfig pentru DNS-over-HTTPS", "download_mobileconfig_dot": "Descărcați .mobileconfig pentru DNS-over-TLS", "download_mobileconfig": "Descărcați fișierul de configurare", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Introduceți limita ratei", "rate_limit": "Limita ratei", "edns_enable": "Activați subrețeaua de clienți EDNS", - "edns_cs_desc": "Trimite subrețelele clienților la serverele DNS.", + "edns_cs_desc": "Adaugă opțiunea EDNS Client Subnet (ECS) la solicitările în amonte și înregistrează valorile trimise de clienți în jurnalul de interogare.", "rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.", "blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare", "blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare", @@ -334,12 +338,12 @@ "install_devices_router_list_4": "Unele tipuri de routere, nu permit configurarea unui server DNS personalizat. În acest caz, configurarea AdGuard Home ca un <0>server DHCPvă poate ajuta. Dacă nu, ar trebui verificat manualul routerului dvs. specific, ca să aflați cum se pot personaliza serverele DNS.", "install_devices_windows_list_1": "Deschideți panoul de control prin meniul Start sau căutare Windows.", "install_devices_windows_list_2": "Accesați categoria \"Rețea și Internet\", apoi la \"Centrul de Rețea și Partajare\".", - "install_devices_windows_list_3": "În partea stângă a ecranului găsiți \"Schimbare setări adaptor\" și clicați pe el.", - "install_devices_windows_list_4": "Selectați conexiunea activă, faceți clic dreapta pe ea și alegeți \"Proprietăți\".", + "install_devices_windows_list_3": "În panoul din stânga, faceți clic pe „Modificare setări adaptor”.", + "install_devices_windows_list_4": "Faceți clic dreapta pe conexiunea activă și selectați „Proprietăți”.", "install_devices_windows_list_5": "Găsiți Internet Protocol Versiunea 4 (TCP/IPv4) din listă, selectați-l și apoi clicați din nou pe Proprietăți.", "install_devices_windows_list_6": "Alegeți „Utilizați următoarele adrese de server DNS” și introduceți adresele serverului dvs. AdGuard Home.", - "install_devices_macos_list_1": "Clicați pe icoana Apple și accesați Preferințele Sistemului.", - "install_devices_macos_list_2": "Clicați pe Network.", + "install_devices_macos_list_1": "Faceți clic pe pictograma „Apple” și accesați „Preferințe de sistem”.", + "install_devices_macos_list_2": "Faceți clic pe „Rețea”.", "install_devices_macos_list_3": "Selectați prima conexiune din listă și clicați pe Avansat.", "install_devices_macos_list_4": "Selectați fila DNS și introduceți adresele serverului dvs. AdGuard Home.", "install_devices_android_list_1": "Din ecranul principal al Meniului Android, tapați Setări.", @@ -349,26 +353,26 @@ "install_devices_android_list_5": "Schimbați valorile DNS 1 și DNS 2 la adresele serverului dvs. AdGuard Home.", "install_devices_ios_list_1": "Din ecranul de start, tapați Setări.", "install_devices_ios_list_2": "Alegeți Wi-Fi în meniul din stânga (este imposibil să configurați DNS pentru rețelele mobile).", - "install_devices_ios_list_3": "Tapați numele rețelei active curente.", + "install_devices_ios_list_3": "Apăsați pe numele rețelei active în prezent.", "install_devices_ios_list_4": "În câmpul DNS, introduceți adresele serverului dvs. AdGuard Home.", "get_started": "Să începem", "next": "Următor", "open_dashboard": "Deschideți Tabloul de bord", "install_saved": "Salvat cu succes", "encryption_title": "Criptare", - "encryption_desc": "Suport de Criptare (HTTPS/TLS) pentru DNS și interfața web administrator", + "encryption_desc": "Suport pentru criptare (HTTPS/TLS) atât pentru DNS, cât și pentru interfața web de administrare", "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", - "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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare.", "topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare.", "form_error_port_range": "Introduceți valoarea portului între 80-65535", - "form_error_port_unsafe": "Acesta este un port nesigur", + "form_error_port_unsafe": "Port nesigur", "form_error_equal": "Nu trebuie să fie egale", "form_error_password": "Parolele nu corespund", "reset_settings": "Resetare setări", @@ -397,7 +401,7 @@ "setup_guide": "Ghid de instalare", "dns_addresses": "Adrese DNS", "dns_start": "Serverul DNS demarează", - "dns_status_error": "Eroare la verificare statut server DNS", + "dns_status_error": "Eroare la verificarea stării serverului DNS", "down": "Down", "fix": "Fix", "dns_providers": "Iată o <0>listă de furnizori DNS cunoscuți ce pot fi aleși.", @@ -405,8 +409,8 @@ "update_failed": "Auto-actualizarea a eșuat. Vă rugăm să urmați aceste etape pentru a actualiza manual.", "manual_update": "Vă rugăm să urmați etapele următoare pentru a actualiza manual.", "processing_update": "Vă rugăm să așteptați, AdGuard Home se actualizează...", - "clients_title": "Clienți", - "clients_desc": "Configură aparatele conectate la AdGuard Home", + "clients_title": "Clienți persistenți", + "clients_desc": "Configurează înregistrările persistente ale clienților pentru dispozitivele conectate la AdGuard Home", "settings_global": "General", "settings_custom": "Personalizat", "table_client": "Client", @@ -417,7 +421,7 @@ "client_edit": "Editare client", "client_identifier": "Identificator", "ip_address": "Adresa IP", - "client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC sau un ID client special (poate fi folosit pentru DoT/DoH/DoQ). <0>Aici puteți afla mai multe despre cum să identificați clienții.", + "client_identifier_desc": "Clienții pot fi identificați prin adresa lor IP, CIDR, adresa MAC sau ClientID (poate fi utilizat pentru DoT/DoH/DoQ). Aflați mai multe despre cum să identificați clienții <0>aici.", "form_enter_ip": "Introduceți IP", "form_enter_subnet_ip": "Introduceți o adresă IP în subrețeaua „{{cidr}}”", "form_enter_mac": "Introduceți MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Nu au fost găsiți clienți", "client_confirm_delete": "Sunteți sigur că doriți să ștergeți clientul \"{{key}}\"?", "list_confirm_delete": "Sigur doriți să ștergeți această listă?", - "auto_clients_title": "Clienți (runtime)", - "auto_clients_desc": "Date despre clienții care folosesc AdGuard Home, dar care nu sunt stocate în configurație", + "auto_clients_title": "Clienți runtime", + "auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home", "access_title": "Setări de acces", - "access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home.", + "access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home", "access_allowed_title": "Clienți autorizați", - "access_allowed_desc": "O listă de adrese CIDR sau IP client. Dacă este configurat, AdGuard Home va accepta cereri numai de la acești clienți.", + "access_allowed_desc": "O listă de CIDR-uri, adrese IP sau ClientID-uri. Dacă această listă are intrări, AdGuard Home va accepta cereri numai de la acești clienți.", "access_disallowed_title": "Clienți neautorizați", - "access_disallowed_desc": "O listă de adrese CIDR sau IP. Dacă este configurat, AdGuard Home va elimina cereri de la acești clienți. Dacă sunt configurați clienți permiși, acest câmp este ignorat.", + "access_disallowed_desc": "O listă de CIDR-uri, adrese IP sau ClientID-uri. Dacă această listă are intrări, AdGuard Home va renunța la cererile de la acești clienți. Acest câmp este ignorat dacă există intrări în „Clienți permiși”.", "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", @@ -475,7 +479,7 @@ "dns_rewrites": "Rescrieri DNS", "form_domain": "Introduceți un nume de domeniu sau wildcard", "form_answer": "Introduceți adresa IP sau numele de domeniu", - "form_error_domain_format": "Format de răspuns invalid", + "form_error_domain_format": "Format de domeniu invalid", "form_error_answer_format": "Format de răspuns invalid", "configure": "Configurați", "main_settings": "Setări principale", @@ -500,6 +504,7 @@ "interval_days": "{{count}} zi", "interval_days_plural": "{{count}} zile", "domain": "Domeniu", + "ecs": "ECS", "punycode": "Punycode", "answer": "Răspuns", "filter_added_successfully": "Filtrul a fost adăugat cu succes", @@ -517,7 +522,7 @@ "interval_hours_plural": "{{count}} ore", "filters_configuration": "Configurația filtrelor", "filters_enable": "Activați filtrele", - "filters_interval": "Interval de actualizare filtre", + "filters_interval": "Intervalul de actualizare a filtrului", "disabled": "Dezactivat", "username_label": "Nume utilizator", "username_placeholder": "Introduceți nume utilizator", @@ -532,7 +537,7 @@ "netname": "Numele rețelei", "network": "Rețea", "descr": "Descriere", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Aflați mai multe despre crearea propriilor liste hosts.", "blocked_by_response": "Blocat de CNAME sau IP ca răspuns", "blocked_by_cname_or_ip": "Blocat de CNAME sau IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Va efectua aceste sarcini: <0>Dezactivare sistem DNSStubListener <0>Setare adresă server DNS la 127.0.0.1 <0>Înlocuire link simbolic țintă /etc/resolv.conf cu /run/systemd/resolve/resolv.conf <0>Oprire DNSStubListener (reîncărcare servici rezolvat prin sistem)", "autofix_warning_result": "Ca urmare, toate cererile DNS ale sistemul dvs. vor fi procesate în mod implicit de AdGuardHome.", "tags_title": "Etichete", - "tags_desc": "Puteți selecta etichetele care corespund clientului. Etichetele pot fi incluse în regulile de filtrare și vă permit să le aplicați mai exact. <0>Aflați mai multe", + "tags_desc": "Puteți selecta etichetele care corespund clientului. Includeți etichete în regulile de filtrare pentru a le aplica mai precis. <0>Aflați mai multe.", "form_select_tags": "Selectați etichete client", "check_title": "Verificați filtrarea", - "check_desc": "Verificați dacă numele de host este filtrat", + "check_desc": "Verifică dacă numele de host este filtrat.", "check": "Verificați", "form_enter_host": "Introduceți un nume de host", "filtered_custom_rules": "Filtrat prin reguli de filtrare personalizate", @@ -582,7 +587,7 @@ "list_updated": "{{count}} listă actualizată", "list_updated_plural": "{{count}} liste actualizate", "dnssec_enable": "Activați DNSSEC", - "dnssec_enable_desc": "Activați semnalul DNSSEC în interogările DNS de ieșire și verificați rezultatul (este necesar un resolver compatibil DNSSEC).", + "dnssec_enable_desc": "Activați semnalul DNSSEC în interogările DNS de ieșire și verificați rezultatul (este necesar un rezolvator compatibil DNSSEC).", "validated_with_dnssec": "Validat cu DNSSEC", "all_queries": "Toate interogările", "show_blocked_responses": "Blocat", @@ -594,18 +599,18 @@ "allowed": "Permise", "filtered": "Filtrate", "rewritten": "Rescrise", - "safe_search": "Căutare sigură", + "safe_search": "Căutarea sigură", "blocklist": "Lista de blocări", "milliseconds_abbreviation": "ms", "cache_size": "Mărime cache", - "cache_size_desc": "Mărime cache DNS (în octeți)", + "cache_size_desc": "Mărime cache DNS (în octeți).", "cache_ttl_min_override": "Suprascrieți minimum TTL", "cache_ttl_max_override": "Suprascrieți maximum TTL", "enter_cache_size": "Introduceți mărimea cache-ului (bytes)", "enter_cache_ttl_min_override": "Introduceți minimum TTL (secunde)", "enter_cache_ttl_max_override": "Introduceți maximum TTL (secunde)", - "cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS", - "cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS", + "cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS.", + "cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS.", "ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă", "cache_optimistic": "Caching optimistic", "cache_optimistic_desc": "Face ca AdGuard Home să răspundă din cache chiar și atunci când intrările au expirate și de asemenea, încearcă să le reîmprospăteze.", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home va renunța la toate interogările DNS de la acest client.", "filter_allowlist": "AVERTISMENT: Această acțiune va exclude și regula „{{disallowed_rule}}” din lista de clienți permiși.", "last_rule_in_allowlist": "Acest client nu poate fi exclus deoarece excluderea regulii „{{disallowed_rule}}” va DEZACTIVA lista „Clienți acceptați”.", - "experimental": "Experimental", "use_saved_key": "Folosiți cheia salvată anterior", "parental_control": "Control Parental", "safe_browsing": "Navigare în siguranță", - "served_from_cache": "{{value}} (furnizat din cache)" + "served_from_cache": "{{value}} (furnizat din cache)", + "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere" } diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index cd1e8091..6a1b6d66 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -1,7 +1,7 @@ { "client_settings": "Настройки клиентов", - "example_upstream_reserved": "Вы можете указать DNS-сервер <0>для конкретного домена(-ов)", - "example_upstream_comment": "Вы можете указать комментарий", + "example_upstream_reserved": "DNS-сервер <0>для конкретных доменов;", + "example_upstream_comment": "комментарий.", "upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.", "parallel_requests": "Параллельные запросы", "load_balancing": "Распределение нагрузки\n", @@ -43,10 +43,11 @@ "form_error_ip6_format": "Некорректный IPv6-адрес", "form_error_ip_format": "Некорректный IP-адрес", "form_error_mac_format": "Некорректный MAC-адрес", - "form_error_client_id_format": "ID клиента может содержать только цифры, строчные латинские буквы и дефисы", + "form_error_client_id_format": "ClientID может содержать только цифры, строчные латинские буквы и дефисы", "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,8 +67,8 @@ "ip": "IP-адрес", "dhcp_table_hostname": "Имя хоста", "dhcp_table_expires": "Истекает", - "dhcp_warning": "Если вы все равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!", - "dhcp_error": "AdGuard Home не смог определить присутствие других 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-сервер».", "dhcp_lease_added": "Статическая аренда «{{key}}» успешно добавлена", @@ -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": "Добавить чёрный список", @@ -196,29 +197,32 @@ "choose_allowlist": "Выберите списки разрешённых", "enter_valid_blocklist": "Добавьте действующий URL-адрес в чёрный список.", "enter_valid_allowlist": "Добавьте действующий URL-адрес в белый список.", - "form_error_url_format": "Некорректный URL", - "form_error_url_or_path_format": "Некорректный URL или абсолютный путь к списку", - "custom_filter_rules": "Пользовательское правило фильтрации", + "form_error_url_format": "Неверный формат URL", + "form_error_url_or_path_format": "Неверный URL или абсолютный путь к списку", + "custom_filter_rules": "Пользовательские правила фильтрации", "custom_filter_rules_hint": "Вводите по одному правилу на строчку. Вы можете использовать правила блокировки или синтаксис файлов hosts.", "system_host_files": "Системные hosts-файлы", "examples_title": "Примеры", - "example_meaning_filter_block": "заблокировать доступ к домену example.org и всем его поддоменам", - "example_meaning_filter_whitelist": "разблокировать доступ к домену example.org и всем его поддоменам", - "example_meaning_host_block": "Теперь AdGuard Home вернёт 127.0.0.1 для домена example.org (но не для его поддоменов).", - "example_comment": "! Так можно добавлять описание", - "example_comment_meaning": "комментарий", - "example_comment_hash": "# И вот так тоже", - "example_regex_meaning": "блокирует доступ к доменам, соответствующим <0>заданному регулярному выражению", - "example_upstream_regular": "обычный 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_tcp": "обычный DNS (поверх TCP)", + "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": "обычный DNS (поверх UDP);", + "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_tcp": "обычный DNS (поверх TCP);", + "example_upstream_tcp_hostname": "обычный DNS (поверх TCP, с именем хоста);", "all_lists_up_to_date_toast": "Все списки уже обновлены", "updated_upstream_dns_toast": "DNS-серверы успешно обновлены", "dns_test_ok_toast": "Указанные серверы DNS работают корректно", "dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания", + "dns_test_warning_toast": "Upstream «{{key}}» не отвечает на тестовые запросы и может работать некорректно", "unblock": "Разблокировать", "block": "Заблокировать", "disallow_this_client": "Запретить доступ клиенту", @@ -275,9 +279,9 @@ "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": "Различные клиенты могут идентифицироваться по специальному идентификатору клиента. Здесь вы можете узнать больше об идентификации клиентов.", + "client_id": "ClientID", + "client_id_placeholder": "Введите ClientID", + "client_id_desc": "Клиенты могут идентифицироваться по ClientID. Здесь вы можете узнать больше об идентификации клиентов.", "download_mobileconfig_doh": "Скачать .mobileconfig для DNS-over-HTTPS", "download_mobileconfig_dot": "Скачать .mobileconfig для DNS-over-TLS", "download_mobileconfig": "Загрузить файл конфигурации", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Введите rate limit", "rate_limit": "Rate limit", "edns_enable": "Включить отправку EDNS Client Subnet", - "edns_cs_desc": "Отправлять подсети клиентов на DNS-серверы.", + "edns_cs_desc": "Добавлять опцию EDNS Client Subnet (ECS) к запросам к upstream-серверам, а также записывать присланные клиентами значения в журнал.", "rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).", "blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса", "blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса", @@ -326,20 +330,20 @@ "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.", "install_devices_windows_list_2": "Перейдите в «Сеть и интернет», а затем в «Центр управления сетями и общим доступом»", - "install_devices_windows_list_3": "В левой стороне экрана найдите «Изменение параметров адаптера» и кликните по нему.", - "install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите «Свойства».", + "install_devices_windows_list_3": "В левой панели кликните на «Изменение параметров адаптера».", + "install_devices_windows_list_4": "Кликните на ваше активное подключение правой кнопкой мыши и выберите «Свойства».", "install_devices_windows_list_5": "Найдите в списке пункт «IP версии 4 (TCP/IPv4)» (или «IP версии 6 (TCP/IPv6)» для IPv6), выделите его и затем снова нажмите «Свойства».", "install_devices_windows_list_6": "Выберите «Использовать следующие адреса DNS-серверов» и введите адреса серверов AdGuard Home.", - "install_devices_macos_list_1": "Кликните по иконке Apple и перейдите в «Системные настройки».", - "install_devices_macos_list_2": "Кликните по иконке «Сеть».", + "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": "В меню управления нажмите иконку «Настройки».", @@ -356,11 +360,11 @@ "open_dashboard": "Открыть Панель управления", "install_saved": "Успешно сохранено", "encryption_title": "Шифрование", - "encryption_desc": "Поддержка шифрования (HTTPS/TLS) для DNS и веб-интерфейса администрирования", + "encryption_desc": "Поддержка шифрования (HTTPS/QUIC/TLS) для DNS и веб-интерфейса администрирования", "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", @@ -368,7 +372,7 @@ "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_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте.", "encryption_certificates": "Сертификаты", "encryption_certificates_desc": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на <0>{{link}} или вы можете купить его у одного из доверенных Центров Сертификации.", "encryption_certificates_input": "Скопируйте сюда сертификаты в PEM-кодировке.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования.", "topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования.", "form_error_port_range": "Введите номер порта из интервала 80-65535", - "form_error_port_unsafe": "Это небезопасный порт", + "form_error_port_unsafe": "Небезопасный порт", "form_error_equal": "Не должны быть равны", "form_error_password": "Пароли не совпадают", "reset_settings": "Сбросить настройки", @@ -405,7 +409,7 @@ "update_failed": "Ошибка авто-обновления. Пожалуйста, следуйте инструкции для обновления вручную.", "manual_update": "Пожалуйста, следуйте инструкции для обновления вручную.", "processing_update": "Пожалуйста, подождите, AdGuard Home обновляется", - "clients_title": "Клиенты", + "clients_title": "Сохранённые клиенты", "clients_desc": "Настройте устройства, использующие AdGuard Home", "settings_global": "Глобальные", "settings_custom": "Свои", @@ -417,7 +421,7 @@ "client_edit": "Редактировать клиента", "client_identifier": "Идентификатор", "ip_address": "IP-адрес", - "client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу или специальному ID (можно использовать для DoT/DoH/DoQ). <0>Здесь вы можете узнать больше об идентификации клиентов.", + "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", @@ -433,17 +437,17 @@ "client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?", "list_confirm_delete": "Вы уверены, что хотите удалить этот список?", "auto_clients_title": "Клиенты (runtime)", - "auto_clients_desc": "Данные о клиентах, которые используют AdGuard Home, но не хранятся в настройках", + "auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home", "access_title": "Настройки доступа", - "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home.", + "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home", "access_allowed_title": "Разрешённые клиенты", - "access_allowed_desc": "Список CIDR, IP-адресов или ID клиентов. Если он настроен, AdGuard Home будет принимать запросы только от этих клиентов.", + "access_allowed_desc": "Список CIDR, IP-адресов или ClientID. Если в списке есть записи, AdGuard Home будет принимать запросы только от этих клиентов.", "access_disallowed_title": "Запрещённые клиенты", - "access_disallowed_desc": "Список CIDR, IP-адресов или ID клиентов. Если он настроен, AdGuard Home будет игнорировать запросы от этих клиентов. Если настроены разрешённые клиенты, это поле игнорируется.", + "access_disallowed_desc": "Список CIDR, IP-адресов или ClientID. Если в списке есть записи, AdGuard Home будет игнорировать запросы от этих клиентов. Это поле игнорируется, если список разрешённых клиентов содержит записи.", "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", @@ -473,7 +477,7 @@ "rewrite_applied": "Применено правило перезаписи", "rewrite_hosts_applied": "Переписано по правилу файла hosts", "dns_rewrites": "Перезапись DNS-запросов", - "form_domain": "Введите домен", + "form_domain": "Введите имя или маску домена", "form_answer": "Введите IP адрес или домен", "form_error_domain_format": "Некорректный домен", "form_error_answer_format": "Некорректный ответ", @@ -500,6 +504,7 @@ "interval_days": "{{count}} день", "interval_days_plural": "{{count}} дней", "domain": "Домен", + "ecs": "ECS", "punycode": "Punycode", "answer": "Ответ", "filter_added_successfully": "Список успешно добавлен", @@ -507,7 +512,7 @@ "filter_updated": "Список успешно обновлён", "statistics_configuration": "Конфигурация статистики", "statistics_retention": "Сохранение статистики", - "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть утеряны", + "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть потеряны", "statistics_clear": "Очистить статистику", "statistics_clear_confirm": "Вы уверены, что хотите очистить статистику?", "statistics_retention_confirm": "Вы уверены, что хотите изменить срок хранения статистики? При сокращении интервала данные могут быть утеряны", @@ -532,7 +537,7 @@ "netname": "Название сети", "network": "Сеть", "descr": "Описание", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Узнайте больше о создании собственных списков блокировки хостов.", "blocked_by_response": "Заблокировано по CNAME или IP в ответе", "blocked_by_cname_or_ip": "Заблокировано с помощью CNAME или IP", @@ -552,10 +557,10 @@ "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": "Теги", - "tags_desc": "Вы можете выбрать теги, которые соответствуют клиенту. Теги могут быть включены в правила фильтрации и позволят вам применять их более точно. <0>Узнать больше.", + "tags_desc": "Вы можете выбрать теги, которые соответствуют клиенту. Теги могут быть включены в правила фильтрации, чтобы применять их более точно. <0>Узнать больше.", "form_select_tags": "Выбрать теги клиента", "check_title": "Проверить фильтрацию", - "check_desc": "Проверить фильтрацию имени хоста", + "check_desc": "Проверить фильтрацию имени хоста.", "check": "Проверить", "form_enter_host": "Введите имя хоста", "filtered_custom_rules": "Отфильтрованы с помощью пользовательских правил фильтрации", @@ -586,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": "Заблокировано Родительским контролем", @@ -598,15 +603,15 @@ "blocklist": "Чёрный список", "milliseconds_abbreviation": "мс", "cache_size": "Размер кеша", - "cache_size_desc": "Размера кеша DNS (в байтах)", - "cache_ttl_min_override": "Переопределить минимальный TTL (в секундах)", - "cache_ttl_max_override": "Переопределить максимальный TTL (в секундах)", + "cache_size_desc": "Размера кеша DNS (в байтах).", + "cache_ttl_min_override": "Переопределить минимальный TTL", + "cache_ttl_max_override": "Переопределить максимальный TTL", "enter_cache_size": "Введите размер кеша (в байтах)", "enter_cache_ttl_min_override": "Введите минимальный TTL (в секундах)", "enter_cache_ttl_max_override": "Введите максимальный TTL (в секундах)", - "cache_ttl_min_override_desc": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов", - "cache_ttl_max_override_desc": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кэше", - "ttl_cache_validation": "Минимальное значение TTL-кеша должно быть меньше или равно максимальному значению", + "cache_ttl_min_override_desc": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов.", + "cache_ttl_max_override_desc": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кеше.", + "ttl_cache_validation": "Значение для переопределения минимального TTL должно быть меньше или равно значению для переопределения максимального", "cache_optimistic": "Оптимистическое кеширование", "cache_optimistic_desc": "AdGuard Home будет отвечать из кеша, даже если ответы в нём неактуальны, и попытается обновить их.", "filter_category_general": "Общие", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home AdGuard Home сбросит все DNS-запросы от этого клиента.", "filter_allowlist": "ВНИМАНИЕ: Это действие также исключит правило «{{disallowed_rule}}» из списка разрешённых клиентов.", "last_rule_in_allowlist": "Нельзя заблокировать этого клиента, так как исключение правила «{{disallowed_rule}}» ОТКЛЮЧИТ режим белого списка.", - "experimental": "Экспериментальный", "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/__locales/si-lk.json b/client/src/__locales/si-lk.json index e9f02cb2..17802771 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -1,36 +1,54 @@ { - "client_settings": "අනුග්‍රාහක සැකසුම්", - "example_upstream_comment": "ඔබට අදහසක් සඳහන් කළ හැකිය", + "client_settings": "අනුග්‍රාහකයේ සැකසුම්", + "example_upstream_comment": "අදහසක්.", "parallel_requests": "සමාන්තර ඉල්ලීම්", "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_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න", "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න", + "use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය", "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක සඳහා පරීක්‍ෂා කරන්න", "save_config": "වින්‍යාසය සුරකින්න", "enabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය සබල කෙරිණි", "disabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය අබල කෙරිණි", + "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 සැකසුම්", - "form_error_required": "අවශ්‍ය ක්ෂේත්‍රයකි", - "form_error_ip4_format": "වලංගු නොවන IPv4 ලිපිනයකි", - "form_error_ip4_range_start_format": "පරාසය ආරම්භයේ අ.ජා.කෙ.4 ලිපිනය වලංගු නොවේ", - "form_error_ip4_range_end_format": "පරාසය අවසානයේ අ.ජා.කෙ.4 ලිපිනය වලංගු නොවේ", + "form_error_required": "ඇවැසි ක්‍ෂේත්‍රයකි", + "form_error_ip4_format": "IPv4 ලිපිනය වලංගු නොවේ", + "form_error_ip4_range_start_format": "පරාසය ආරම්භයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි", + "form_error_ip4_range_end_format": "පරාසය අවසානයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි", "form_error_ip6_format": "වලංගු නොවන අ.ජා.කෙ.6 ලිපිනයකි", "form_error_ip_format": "අ.ජා.කෙ. (IP) ලිපිනය වලංගු නොවේ", "form_error_mac_format": "මා.ප්‍ර.පා. ලිපිනය වලංගු නොවේ", "form_error_client_id_format": "අනුග්‍රාහකයේ හැඳු. වලංගු නොවේ", - "form_error_server_name": "වලංගු නොවන සේවාදායක නාමයකි", + "form_error_server_name": "සේවාදායකයේ නම වලංගු නොවේ", + "form_error_subnet": "\"{{cidr}}\" අනුජාලය හි \"{{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": "ලිපින එක් අනුජාලයක තිබිය යුතුය", "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) ලිපින", @@ -38,9 +56,17 @@ "dhcp_table_hostname": "ධාරක නාමය", "dhcp_table_expires": "කල් ඉකුත් වීම", "dhcp_warning": "ඔබට කෙසේ හෝ ග.ධා.වි.කෙ. සේවාදායකය සබල කිරීමට අවශ්‍ය නම්, ඔබගේ ජාලයේ වෙනත් ක්‍රියාකාරී ග.ධා.වි.කෙ. සේවාදායකයක් නැති බව තහවුරු කරගන්න. මෙය සම්බන්ධිත උපාංග සඳහා අන්තර්ජාලය බිඳ දැමිය හැකිය!", - "dhcp_error": "ජාලයේ තවත් ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය.", + "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": "නගරය", @@ -56,7 +82,7 @@ "settings": "සැකසුම්", "filters": "පෙරහන්", "filter": "පෙරහන", - "query_log": "විමසුම් ලොගය", + "query_log": "විමසුම් සටහන", "compact": "සංක්ෂිප්ත", "nothing_found": "කිසිවක් හමු නොවිණි", "faq": "නිති පැණ", @@ -78,7 +104,7 @@ "blocked_by": "<0>පෙරහන් මගින් අවහිර කළ", "stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්", "stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි", - "stats_query_domain": "ජනප්‍රිය විමසන ලද වසම්", + "stats_query_domain": "ප්‍රචලිත විමසන ලද වසම්", "for_last_24_hours": "පසුගිය පැය 24 සඳහා", "for_last_days": "පසුගිය දවස් {{count}} සඳහා", "for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා", @@ -86,8 +112,8 @@ "stats_disabled_short": "සංඛ්‍යාලේඛන අබල කර ඇත", "no_domains_found": "වසම් කිසිවක් හමු නොවිණි", "requests_count": "ඉල්ලීම් ගණන", - "top_blocked_domains": "ජනප්‍රිය අවහිර කළ වසම්", - "top_clients": "ජනප්‍රිය අනුග්‍රාහක", + "top_blocked_domains": "ප්‍රචලිත අවහිර කළ වසම්", + "top_clients": "ප්‍රචලිත අනුග්‍රාහක", "no_clients_found": "අනුග්‍රාහක හමු නොවිණි", "general_statistics": "පොදු සංඛ්‍යාලේඛන", "number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", @@ -103,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": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න", @@ -157,23 +184,26 @@ "enter_valid_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුල් කරන්න.", "enter_valid_allowlist": "ඉඩ දීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුල් කරන්න.", "form_error_url_format": "වලංගු නොවන ඒ.ස.නි.(URL) ආකෘතියකි", - "form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි", + "form_error_url_or_path_format": "වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි", "custom_filter_rules": "අභිරුචි පෙරීමේ නීති", "custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුල් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.", "system_host_files": "පද්ධතියේ සත්කාරක ගොනු", "examples_title": "උදාහරණ", - "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අවහිර කරයි", - "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අනවහිර කරයි", - "example_meaning_host_block": "ඇඩ්ගාර්ඩ් හෝම් දැන් උදාහරණය.ලංකා වසම සඳහා 127.0.0.1 ලිපිනය ලබා දෙනු ඇත (නමුත් එහි උප ලිපින නොවේ).", - "example_comment": "! මෙතැන අදහස් දැක්වීමක්", - "example_comment_meaning": "අදහසක්", - "example_comment_hash": "# එසේම අදහස් දැක්වීමක්", - "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැලපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි", - "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා)", - "example_upstream_dot": "සංකේතිත <0>DNS-over-TLS", - "example_upstream_doh": "සංකේතිත <0>DNS-over-HTTPS", - "example_upstream_doq": "සංකේතිත <0>DNS-over-QUIC", - "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා) ", + "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අවහිර කරයි;", + "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අනවහිර කරයි;", + "example_meaning_host_block": "උදාහරණය.ලංකා වසම සඳහා 127.0.0.1 සමඟ ප්‍රතිචාර දක්වයි (නමුත් එහි උප ලිපින සඳහා නොවේ);", + "example_comment": "! මෙතැන අදහස් දැක්වීමක්.", + "example_comment_meaning": "අදහසක්;", + "example_comment_hash": "# එසේම අදහස් දැක්වීමක්.", + "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.", + "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);", + "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": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);", "all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි", "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි", "dns_test_not_ok_toast": "\"{{key}}\" සේවාදායක(ය): භාවිතා කිරීමට නොහැකි විය, ඔබ එය නිවැරදිව ලියා ඇතිදැයි පරීක්‍ෂා කරන්න", @@ -194,6 +224,7 @@ "empty_response_status": "හිස්", "show_all_filter_type": "සියල්ල පෙන්වන්න", "show_filtered_type": "පෙරූ දෑ පෙන්වන්න", + "no_logs_found": "සටහන් හමු නොවිණි", "refresh_btn": "නැවුම් කරන්න", "previous_btn": "පෙර", "next_btn": "ඊළඟ", @@ -205,20 +236,21 @@ "rule_added_to_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළට මෙම නීතිය එකතු කෙරිණි: {{rule}}", "query_log_response_status": "තත්වය: {{value}}", "query_log_filtered": "{{filter}} මගින් පෙරිණි", - "query_log_confirm_clear": "සම්පූර්ණ විමසුම් ලොගය ඉවත් කිරීමට අවශ්‍ය යැයි ඔබට විශ්වාසද?", - "query_log_updated": "විමසුම් ලොගය සාර්ථකව යාවත්කාලීන කරන ලදි", - "query_log_clear": "විමසුම් ලොග ඉවත් කරන්න", - "query_log_retention": "විමසුම් ලොග රඳවා තබා ගැනීම", - "query_log_enable": "ලොගය සබල කරන්න", - "query_log_configuration": "ලොග වින්‍යාසය", - "query_log_disabled": "විමසුම් ලොගය අබල කර ඇති අතර එය <0>සැකසුම් තුළ වින්‍යාසගත කළ හැකිය", + "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": "විමසුම් ලොගය රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත", + "query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත", "anonymize_client_ip": "අනුග්‍රාහකයෙහි අ.ජා.කෙ. (IP) නිර්නාමික කරන්න", - "anonymize_client_ip_desc": "ලොග සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ සම්පූර්ණ අ.ජා.කෙ. ලිපිනය සුරැකීමෙන් වලකින්න", + "anonymize_client_ip_desc": "සටහන් සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ පූර්ණ අ.ජා.කෙ. ලිපිනය සුරකින්න එපා", "dns_config": "ව.නා.ප. සේවාදායක වින්‍යාසය", "dns_cache_config": "ව.නා.ප. නිහිත වින්‍යාසය", - "dns_cache_config_desc": "මෙහිදී ඔබට ව.නා.ප. නිහිතය වින්‍යාසගත කළ හැකිය", + "dns_cache_config_desc": "මෙතැන ඔබට ව.නා.ප. නිහිතය වින්‍යාසගත කළ හැකිය", "blocking_mode": "අවහිර කරන ආකාරය", "default": "සුපුරුදු", "nxdomain": "නොපවතින වසම", @@ -227,8 +259,15 @@ "custom_ip": "අභිරුචි අ.ජා.කෙ.", "blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම", "blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම", - "client_id": "අනුග්‍රාහකයේ හැඳුනුම", - "client_id_placeholder": "අනුග්‍රාහකයේ හැඳුනුම යොදන්න", + "dnscrypt": "DNSCrypt", + "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": "වින්‍යාසගත ගොනුව බාගන්න", "plain_dns": "සරල ව.නා.ප.", "form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න", @@ -255,12 +294,12 @@ "install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකයකි. ඔබගේ මුළු ජාලය සහ සියළුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්‍ය නොවේ.", "install_settings_title": "පරිපාලක වියමන අතුරු මුහුණත", "install_settings_listen": "සවන් දෙන අතුරු මුහුණත", - "install_settings_port": "කෙවෙනිය", + "install_settings_port": "තොට", "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණතට පහත ලිපින වලින් ප්‍රවේශ වීමට හැකිය:", - "form_error_port": "වලංගු කෙවෙනියක අගයක් යොදන්න", + "form_error_port": "වලංගු තොටක අගයක් යොදන්න", "install_settings_dns": "ව.නා.ප. සේවාදායකය", "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:", - "install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්", + "install_settings_all_interfaces": "සියළු අතුරුමුහුණත්", "install_auth_title": "සත්‍යාපනය", "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලන වියමන අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කළ යුතුය. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.", "install_auth_username": "පරිශීලක නාමය", @@ -274,22 +313,24 @@ "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": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය තෝරන්න, එය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.", + "install_devices_windows_list_3": "වම් තීරුවෙහි \"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" ඔබන්න.", + "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.", "install_devices_windows_list_5": "ලැයිස්තුවෙන් \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 4 (TCP/IPv4)\" (හෝ, IPv6 සඳහා, \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 6 (TCP/IPv6)\") සොයාගෙන එය තෝරා ඉන්පසු ගුණාංග මත නැවත ඔබන්න.", "install_devices_windows_list_6": "'පහත සඳහන් ව.නා.ප. සේවාදායක ලිපින භාවිතා කරන්න' යන්න තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.", "install_devices_macos_list_1": "ඇපල් නිරූපකය එබීමෙන් පසු පද්ධතියේ මනාප වෙත යන්න.", "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 පිහිටුවීම් අගයන් ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින වලට වෙනස් කරන්න.", @@ -302,26 +343,28 @@ "open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න", "install_saved": "සාර්ථකව සුරකින ලදි", "encryption_title": "සංකේතනය", - "encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි", + "encryption_desc": "ගුප්තකේතනය (HTTPS/QUIC/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි", "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' ස්ථානයේ DNS-over-HTTPS ද ලබා දෙනු ඇත.", - "encryption_dot": "DNS-over-TLS කෙවෙනිය", - "encryption_dot_desc": "මෙම කෙවෙනිය වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා DNS-over-TLS සේවාදායකයක් ධාවනය කරනු ඇත.", - "encryption_doq": "DNS-over-QUIC කෙවෙනිය", - "encryption_doq_desc": "මෙම කෙවෙනිය වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කෙවෙනිය හරහා DNS-over-QUIC සේවාදායකයක් ධාවනය කරනු ඇත. එය පර්යේෂණාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්‍රාහකයින් නැත.", + "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_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}} පුද්ගලික යතුරකි", @@ -332,24 +375,25 @@ "encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", "topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතන සැකසුම් යාවත්කාල කරන්න.", "topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතන සැකසුම් යාවත්කාල කරන්න.", - "form_error_port_range": "80-65535 පරාසය හි කෙවෙනියක අගයක් ඇතුල් කරන්න", - "form_error_port_unsafe": "මෙය අනාරක්ෂිත කෙවෙනියකි", + "form_error_port_range": "80-65535 පරාසය හි තොටක අගයක් ඇතුල් කරන්න", + "form_error_port_unsafe": "මෙය අනාරක්‍ෂිත තොටකි", "form_error_equal": "සමාන නොවිය යුතුය", - "form_error_password": "මුරපදය නොගැලපුණි", + "form_error_password": "මුරපදය නොගැළපේ", "reset_settings": "සැකසුම් යළි පිහිටුවන්න", "update_announcement": "ඇඩ්ගාර්ඩ් හෝම් {{version}} දැන් ලබා ගත හැකිය! වැඩි විස්තර සඳහා <0>මෙය ඔබන්න.", "setup_guide": "පිහිටුවීමේ මාර්ගෝපදේශය", "dns_addresses": "ව.නා.ප. ලිපින", "dns_start": "ව.නා.ප. සේවාදායකය ආරම්භ වෙමින්", - "dns_status_error": "ව.නා.ප. සේවාදායකයේ තත්වය පරීක්‍ෂා කිරීමේදී දෝෂයකි", + "dns_status_error": "ව.නා.ප. සේවාදායකයේ තත්‍වය පරීක්‍ෂා කිරීමේ දෝෂයකි", "down": "බිඳ වැටී", "fix": "නිරාකරණය", "dns_providers": "මෙහි තෝරා ගැනීමට <0>දැනුවත් ව.නා.ප. සපයන්නන්ගේ ලැයිස්තුවක් ඇත.", "update_now": "යාවත්කාල කරන්න", "update_failed": "ස්වයං යාවත්කාලය අසමත් විය. අතින් යාවත්කාල කිරීමට කරුණාකර පියවර අනුගමනය කරන්න.", + "manual_update": "අතින් යාවත්කාල කිරීමට මෙම පියවර අනුගමනය කරන්න.", "processing_update": "රැඳී සිටින්න, ඇඩ්ගාර්ඩ් හෝම් යාවත්කාල වෙමින්", - "clients_title": "අනුග්‍රාහක", - "clients_desc": "ඇඩ්ගාර්ඩ් හෝම් වෙත සම්බන්ධිත උපාංග වින්‍යාසගත කරන්න", + "clients_title": "නිබැඳි අනුග්‍රාහක", + "clients_desc": "ඇඩ්ගාර්ඩ් හෝම් වෙත සම්බන්ධිත උපාංග සඳහා නිබැඳි අනුග්‍රාහක වාර්තා වින්‍යාසගත කරන්න", "settings_global": "ගෝලීය", "settings_custom": "අභිරුචි", "table_client": "අනුග්‍රාහකය", @@ -360,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) යොදන්න", @@ -374,28 +419,36 @@ "clients_not_found": "අනුග්‍රාහක හමු නොවිණි", "client_confirm_delete": "\"{{key}}\" අනුග්‍රාහකය ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", "list_confirm_delete": "මෙම ලැයිස්තුව ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", - "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන අනුග්‍රාහක දත්ත, නමුත් වින්‍යාසය තුළ ගබඩා කර නැති", + "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන නිබැඳි අනුග්‍රාහක තුළ නැති උපාංග", "access_title": "ප්‍රවේශවීමට සැකසුම්", - "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය සඳහා ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය.", + "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_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}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?", @@ -429,6 +482,7 @@ "interval_days": "දවස් {{count}}", "interval_days_plural": "දවස් {{count}}", "domain": "වසම", + "ecs": "ECS", "answer": "උත්තරය", "filter_added_successfully": "පෙරහන සාර්ථකව එකතු කෙරිණි", "filter_removed_successfully": "ලැයිස්තුව සාර්ථකව ඉවත් කෙරිණි", @@ -436,8 +490,8 @@ "statistics_configuration": "සංඛ්‍යාලේඛන වින්‍යාසය", "statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම", "statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත", - "statistics_clear": " සංඛ්‍යාලේඛන ඉවත් කරන්න", - "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?", + "statistics_clear": "සංඛ්‍යාලේඛන හිස් කරන්න", + "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට වුවමනා ද?", "statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත", "statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කෙරිණි", "statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න", @@ -475,11 +529,11 @@ "fastest_addr_desc": "සියළුම ව.නා.ප. සේවාදායක වලින් විමසා සියළු ප්‍රතිචාර අතරින් වේගවත්ම අ.ජා.කෙ. ලිපිනය ලබා දෙයි. සියළුම ව.නා.ප. ප්‍රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.", "autofix_warning_text": "ඔබ \"නිරාකරණය\" යන්න එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතයට වින්‍යාසගත කෙරෙනු ඇත.", "autofix_warning_result": "ප්‍රතිඵලයක් ලෙස ඔබගේ පද්ධතියෙන් ලැබෙන සියළුම ව.නා.ප. ඉල්ලීම් මූලිකවම ඇඩ්ගාර්ඩ් හෝම් විසින් සකසනු ඇත.", - "tags_title": "හැඳුනුම් සංකේත", - "tags_desc": "අනුග්‍රාහකයට අනුරූප වන හැඳුනුම් සංකේත ඔබට තෝරා ගත හැකිය. පෙරහන් නීති වලට හැඳුනුම් සංකේත ඇතුළත් කළ හැකි අතර ඒවා වඩාත් නිවැරදිව යෙදීමට ඔබට ඉඩ සලසයි. <0>තව දැන ගන්න", - "form_select_tags": "අනුග්‍රාහක හැඳුනුම් සංකේත", + "tags_title": "අනන්‍යන", + "tags_desc": "අනුග්‍රාහකයට අනුරූපව අනන්‍යන ඔබට තෝරා ගත හැකිය. ඒවා වඩාත් නිවැරදිව යෙදීමට \nඅනන්‍යන පෙරහන් නීති වලට ඇතුළත් කරන්න. <0>තව දැන ගන්න.", + "form_select_tags": "අනුග්‍රාහක අනන්‍යන තෝරන්න", "check_title": "පෙරීම පරීක්‍ෂා කරන්න", - "check_desc": "ධාරක නාමය පෙරහන් වේ දැයි පරීක්‍ෂා කරන්න", + "check_desc": "සත්කාරක නාමය පෙරෙනවා දැයි පරීක්‍ෂා කරන්න.", "check": "පරීක්‍ෂාව", "form_enter_host": "ධාරක නාමයක් ඇතුල් කරන්න", "filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි", @@ -505,6 +559,8 @@ "confirm_static_ip": "ඇඩ්ගාර්ඩ් හෝම් ඔබගේ ස්ථිතික අ.ජා.කෙ. (IP) ලිපිනය ලෙස {{ip}} වින්‍යාසගත කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍යද?", "list_updated": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි", "list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි", + "dnssec_enable": "DNSSEC සබල කරන්න", + "validated_with_dnssec": "DNSSEC සමඟ වලංගු කෙරිණි", "all_queries": "සියළුම විමසුම්", "show_blocked_responses": "අවහිර කර ඇත", "show_whitelisted_responses": "ඉඩ දී ඇත", @@ -519,14 +575,15 @@ "blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව", "milliseconds_abbreviation": "මිලි තත්.", "cache_size": "නිහිතයෙහි ප්‍රමාණය", - "cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට වලින්)", + "cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට)", "cache_ttl_min_override": "අවම පව. කා. අභිබවන්න", "cache_ttl_max_override": "උපරිම පව. කා. අභිබවන්න", "enter_cache_size": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය යොදන්න (බයිට)", "enter_cache_ttl_min_override": "අවම පව. කා. (TTL) ඇතුල් කරන්න", "enter_cache_ttl_max_override": "උපරිම පව. කා. (TTL) ඇතුල් කරන්න", - "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි නිවේශිත සඳහා ඉතා වැඩි පවත්නා කාලයක අගයක් (තත්.) සකසන්න", - "ttl_cache_validation": "නිහිතයෙහි අවම පව. කා. (TTL) අගය උපරිම අගයට වඩා අඩු හෝ සමාන විය යුතුය", + "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි නිවේශිත සඳහා උපරිම පවත්නා කාලයක අගයක් (තත්.) සකසන්න.", + "ttl_cache_validation": "නිහිතයෙහි පාගාගෙන යන අවම පව. කා. (TTL) උපරිමයට වඩා අඩු හෝ සමාන විය යුතුය", + "cache_optimistic": "සර්වශුභවාදී නිහිතගතය", "cache_optimistic_desc": "නිවේශිත කල් ඉකුත් වූ විට පවා ඇඩ්ගාර්ඩ් හෝම් ට නිහිතයෙන් ප්‍රතිචාර දැක්වීමට සලස්වයි එමෙන්ම ඒවා නැවත නැවුම් කිරීමට ද උත්සාහ කරයි.", "filter_category_general": "පොදු", "filter_category_security": "ආරක්‍ෂණ", @@ -539,11 +596,13 @@ "setup_config_to_enable_dhcp_server": "ග.ධා.වි.කෙ. සේවාදායකය සබල කිරීමට වින්‍යාසය පිහිටුවන්න", "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": "ඇඩ්ගාර්ඩ් හෝම් මෙම අනුග්‍රාහකයේ සියළුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.", - "experimental": "පරීක්‍ෂාත්මක", + "filter_allowlist": "අවවාදයයි: මෙම ක්‍රියාමාර්ගය ඉඩලත් අනුග්‍රාහක ලේඛන වලින්ද \"{{disallowed_rule}}\" නීතිය බැහැර කරයි.", + "last_rule_in_allowlist": "\"{{disallowed_rule}}\" නීතිය බැහැර කිරීම \"ඉඩලත් අනුග්‍රාහක\" ලේඛනය අබල කරන බැවින් මෙම අනුග්‍රාහකය ඉඩ නොදීමට නොහැකිය.", "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/__locales/sk.json b/client/src/__locales/sk.json index 7b4596f3..b4fa2955 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -1,7 +1,7 @@ { "client_settings": "Nastavenie klienta", - "example_upstream_reserved": "Môžete zadať DNS upstream <0>pre konkrétnu doménu (domény)", - "example_upstream_comment": "Môžete napísať komentár", + "example_upstream_reserved": "upstream <0>pre konkrétne domény;", + "example_upstream_comment": "komentár.", "upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.", "parallel_requests": "Paralelné dopyty", "load_balancing": "Vyrovnávanie záťaže", @@ -35,7 +35,7 @@ "dhcp_config_saved": "Konfigurácia DHCP servera bola úspešne uložená", "dhcp_ipv4_settings": "Nastavenia DHCP IPv4", "dhcp_ipv6_settings": "Nastavenia DHCP IPv6", - "form_error_required": "Povinná položka", + "form_error_required": "Povinná položka.", "form_error_ip4_format": "Neplatná IPv4 adresa", "form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu", "form_error_ip4_range_end_format": "Neplatný koniec rozsahu IPv4 formátu", @@ -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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Meno hostiteľa", "dhcp_table_expires": "Vyprší", "dhcp_warning": "Ak chcete server DHCP napriek tomu zapnúť, uistite sa, že v sieti nie je žiadny iný aktívny DHCP server. V opačnom prípade sa môže prerušiť internet pre už pripojené zariadenia!", - "dhcp_error": "AdGuard Home nevie určiť, či je v sieti iný aktívny DHCP server.", + "dhcp_error": "AdGuard Home nevie určiť, či je v sieti iný aktívny DHCP server", "dhcp_static_ip_error": "Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. AdGuard Home nedokázal určiť, či je toto sieťové rozhranie nakonfigurované pomocou statickej adresy IP. Nastavte statickú IP adresu manuálne.", "dhcp_dynamic_ip_found": "Váš systém používa pre rozhranie <0>{{interfaceName}} dynamickú konfiguráciu IP adresy. Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. Vaša aktuálna IP adresa je <0>{{ipAddress}}. ak Ak stlačíte tlačidlo \"Povoliť DHCP server\", AdGuard Home automaticky nastaví túto IP adresu ako statickú.", "dhcp_lease_added": "Statický \"{{key}}\" prenájmu bol úspešne pridaný", @@ -167,8 +168,8 @@ "enabled_safe_browsing_toast": "Bezpečné prehliadanie zapnuté", "disabled_parental_toast": "Vypnutá Rodičovská kontrola", "enabled_parental_toast": "Zapnutá Rodičovská kontrola", - "disabled_safe_search_toast": "Vypnuté bezpečné vyhľadávanie", - "enabled_save_search_toast": "Zapnuté bezpečné vyhľadávanie", + "disabled_safe_search_toast": "Vypnuté Bezpečné vyhľadávanie", + "enabled_save_search_toast": "Zapnuté Bezpečné vyhľadávanie", "enabled_table_header": "Zapnuté", "name_table_header": "Meno", "list_url_table_header": "Zoznam URL adries", @@ -202,23 +203,26 @@ "custom_filter_rules_hint": "Zadajte na každý riadok jedno pravidlo. Môžete použiť buď adblock pravidlá alebo syntax host súborov.", "system_host_files": "Systémové súbory hosts", "examples_title": "Príklady", - "example_meaning_filter_block": "zablokovať prístup k doméne example.org a všetkým jej subdoménam", - "example_meaning_filter_whitelist": "odblokovať prístup k doméne example.org a všetkým jej subdoménam", - "example_meaning_host_block": "AdGuard Home teraz vráti adresu 127.0.0.1 pre doménu example.org (ale nie pre jej subdomény).", - "example_comment": "! Sem sa pridáva komentár", - "example_comment_meaning": "len komentár", - "example_comment_hash": "# Tiež komentár", - "example_regex_meaning": "zablokovať prístup k doménam, ktoré zodpovedajú zadanému regulárnemu výrazu", - "example_upstream_regular": "radová DNS (cez UDP)", - "example_upstream_dot": "šifrované <0>DNS-over-TLS", - "example_upstream_doh": "šifrované <0>DNS-over-HTTPS", - "example_upstream_doq": "šifrované <0>DNS-over-QUIC", - "example_upstream_sdns": "môžete použiť <0>DNS pečiatky pre <1>DNSCrypt alebo <2>DNS-over-HTTPS", - "example_upstream_tcp": "radová DNS (cez TCP)", + "example_meaning_filter_block": "zablokovať prístup k doméne example.org a všetkým jej subdoménam;", + "example_meaning_filter_whitelist": "odblokovať prístup k doméne example.org a všetkým jej subdoménam;", + "example_meaning_host_block": "vrátiť IP adresu 127.0.0.1 pre doménu example.org (ale nie pre jej subdomény);", + "example_comment": "! Sem sa pridáva komentár.", + "example_comment_meaning": "len komentár;", + "example_comment_hash": "# Tiež komentár.", + "example_regex_meaning": "zablokovať prístup k doménam zodpovedajúcim zadanému regulárnemu výrazu.", + "example_upstream_regular": "obyčajná DNS (cez UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Všetky zoznamy sú už aktuálne", "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", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-over-QUIC", "client_id": "ID klienta", "client_id_placeholder": "Zadať ID klienta", - "client_id_desc": "Rôznych klientov možno identifikovať podľa špeciálneho ID klienta. Tu sa dozviete viac o tom, ako identifikovať klientov.", + "client_id_desc": "Klientov možno identifikovať podľa ClientID. Viac informácií o tom, ako identifikovať klientov, nájdete tu.", "download_mobileconfig_doh": "Prevziať .mobileconfig pre DNS-over-HTTPS", "download_mobileconfig_dot": "Prevziať .mobileconfig pre DNS-over-TLS", "download_mobileconfig": "Stiahnuť konfiguračný súbor", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Zadajte rýchlostný limit", "rate_limit": "Rýchlostný limit", "edns_enable": "Povoliť klientsku podsiete EDNS", - "edns_cs_desc": "Posiela podsiete klientov na DNS servery.", + "edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream požiadaviek a zapíše hodnoty odoslané klientmi do denníka dopytov.", "rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.", "blocking_ipv4_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti A", "blocking_ipv6_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti AAAA", @@ -334,11 +338,11 @@ "install_devices_router_list_4": "Na niektorých typoch smerovačov nemôžete nastaviť vlastný DNS server. V takom prípade môže pomôcť, ak nastavíte AdGuard Home ako <0>DHCP server. V opačnom prípade by ste mali vyhľadať príručku, ako prispôsobiť DNS servery konkrétnemu modelu smerovača.", "install_devices_windows_list_1": "Otvorte panel Nastavenia cez menu Štart alebo vyhľadávanie Windows.", "install_devices_windows_list_2": "Prejdite do kategórie Sieť a internet a potom do Centra sietí a zdieľania.", - "install_devices_windows_list_3": "Na ľavej strane obrazovky nájdite položku \"Zmeniť nastavenia adaptéra\" a kliknite na ňu.", - "install_devices_windows_list_4": "Zvoľte aktívne pripojený adaptér a pravým klikom otvorte Vlastnosti", + "install_devices_windows_list_3": "Na ľavom paneli kliknite na „Zmeniť nastavenia adaptéra“.", + "install_devices_windows_list_4": "Kliknite pravým tlačidlom myši na aktívne pripojenie a vyberte Vlastnosti.", "install_devices_windows_list_5": "Nájdite v zozname položku \"Internet Protocol verzia 4 (TCP/IPv4)\" (alebo pre IPv6, \"Internet Protocol verzia 6 (TCP/IPv6)\"), vyberte ju a potom znova kliknite na Vlastnosti.", "install_devices_windows_list_6": "Zvoľte \"Použiť nasledujúce adresy DNS servera\" a zadajte adresy domáceho AdGuard servera.", - "install_devices_macos_list_1": "Kliknite na ikonu Apple a prejdite na položku Systémové predvoľby.", + "install_devices_macos_list_1": "Kliknite na ikonu Apple a prejdite na Predvoľby systému.", "install_devices_macos_list_2": "Kliknite na Sieť.", "install_devices_macos_list_3": "Zvoľte prvé pripojenie vo Vašom zozname a kliknite na Pokročilé.", "install_devices_macos_list_4": "Vyberte kartu DNS a zadajte adresy Vašich AdGuard Home serverov.", @@ -360,7 +364,7 @@ "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", @@ -368,7 +372,7 @@ "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": "Port DNS-cez-QUIC", - "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_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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania.", "topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania.", "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535", - "form_error_port_unsafe": "Toto nie je bezpečný port", + "form_error_port_unsafe": "Nezabezpečený port", "form_error_equal": "Nesmie byť rovnaká", "form_error_password": "Heslo sa nezhoduje", "reset_settings": "Obnoviť nastavenia", @@ -405,8 +409,8 @@ "update_failed": "Automatická aktualizácia zlyhala. Prosím sledujte postup pre manuálnu aktualizáciu.", "manual_update": "Pre manuálnu aktualizáciu prosím sledujte tento postup.", "processing_update": "Čakajte prosím, AdGuard Home sa aktualizuje", - "clients_title": "Klienti", - "clients_desc": "Konfigurácia zariadení pripojených k AdGuard Home", + "clients_title": "Permanentní klienti", + "clients_desc": "Nakonfigurujte trvalé záznamy klientov pre zariadenia pripojené k AdGuard Home", "settings_global": "Globálne", "settings_custom": "Vlastné", "table_client": "Klient", @@ -417,7 +421,7 @@ "client_edit": "Upraviť klienta", "client_identifier": "Identifikátor", "ip_address": "IP adresa", - "client_identifier_desc": "Klientov je možné identifikovať podľa IP adresy, CIDR a MAC adresy alebo špeciálneho ID klienta (možno použiť pre DoT/DoH/DoQ). <0>Tu sa dozviete viac o tom, ako identifikovať klientov.", + "client_identifier_desc": "Klientov možno identifikovať podľa ich IP adresy, CIDR, MAC adresy alebo ClientID (možno použiť pre DoT/DoH/DoQ). Viac informácií o tom, ako identifikovať klientov, nájdete <0>tu.", "form_enter_ip": "Zadajte IP adresu", "form_enter_subnet_ip": "Zadajte IP adresu do podsiete \"{{cidr}}\"", "form_enter_mac": "Zadajte MAC adresu", @@ -432,18 +436,18 @@ "clients_not_found": "Nebol nájdený žiaden klient", "client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?", "list_confirm_delete": "Naozaj chcete vymazať tento zoznam?", - "auto_clients_title": "Klienti (runtime)", - "auto_clients_desc": "Údaje o klientoch, ktorí používajú AdGuard Home, ale nie sú uložení v konfigurácii", + "auto_clients_title": "Runtime klienti", + "auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home", "access_title": "Nastavenia prístupu", "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.", "access_allowed_title": "Povolení klienti", - "access_allowed_desc": "Zoznam CIDR, IP adries alebo ID klientov. Ak je nakonfigurovaný, AdGuard Home akceptuje len dopyty od týchto klientov.", + "access_allowed_desc": "Zoznam CIDR, IP adries alebo ClientID. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať požiadavky iba od týchto klientov.", "access_disallowed_title": "Nepovolení klienti", - "access_disallowed_desc": "Zoznam CIDR, IP adries alebo ID klientov. Ak je nakonfigurovaný, AdGuard Home bude ignorovať dopyty od týchto klientov.", + "access_disallowed_desc": "Zoznam CIDR, IP adries alebo ClientID. Ak tento zoznam obsahuje položky, AdGuard Home zruší požiadavky od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} deň", "interval_days_plural": "{{count}} dní", "domain": "Doména", + "ecs": "ECS", "punycode": "Punycode", "answer": "Odpoveď", "filter_added_successfully": "Filter bol úspešne pridaný", @@ -532,7 +537,7 @@ "netname": "Meno siete", "network": "Sieť", "descr": "Popis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Dozvedieť sa viac o tvorbe vlastných zoznamov hostiteľov.", "blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi", "blocked_by_cname_or_ip": "Zablokované na základe CNAME alebo IP", @@ -552,10 +557,10 @@ "autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener <0>Nastaviť adresu servera DNS na 127.0.0.1 <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)", "autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.", "tags_title": "Tagy", - "tags_desc": "Môžete vybrať tagy ktoré zodpovedajú klientovi. Tagy môžu byť súčasťou filtračných pravidiel a umožňujú Vám použiť ich presnejšie. <0>Viac informácií", + "tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrovania, aby ste ich použili presnejšie. <0>Viac informácií.", "form_select_tags": "Zvoľte tagy klienta", "check_title": "Skontrolujte filtráciu", - "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný", + "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.", "check": "Kontrola", "form_enter_host": "Zadajte meno hostiteľa", "filtered_custom_rules": "Filtrované podľa vlastných filtračných pravidiel", @@ -604,8 +609,8 @@ "enter_cache_size": "Zadať veľkosť cache (v bajtoch)", "enter_cache_ttl_min_override": "Zadať minimálne TTL (v sekundách)", "enter_cache_ttl_max_override": "Zadať maximálne TTL (v sekundách)", - "cache_ttl_min_override_desc": "Predĺži krátke hodnoty TTL (v sekundách) prijaté od servera typu upstream pri ukladaní odpovedí DNS do cache pamäte", - "cache_ttl_max_override_desc": "Nastaví maximálnu hodnotu TTL (v sekundách) pre záznamy v DNS cache pamäti", + "cache_ttl_min_override_desc": "Predĺži krátke hodnoty TTL (v sekundách) prijaté od servera typu upstream pri ukladaní odpovedí DNS do cache pamäte.", + "cache_ttl_max_override_desc": "Nastaví maximálnu hodnotu TTL (v sekundách) pre záznamy v DNS cache pamäti.", "ttl_cache_validation": "Minimálna hodnota TTL cache musí byť menšia alebo rovná maximálnej hodnote", "cache_optimistic": "Optimistické nastavenie", "cache_optimistic_desc": "Nechajte AdGuard Home odpovedať z vyrovnávacej pamäte, aj keď už platnosť položiek skončila, a tiež sa pokúste ich obnoviť.", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home zruší všetky DNS dopyty od tohto klienta.", "filter_allowlist": "UPOZORNENIE: Táto akcia tiež vylúči pravidlo \"\"{{disallowed_rule}}\"\" zo zoznamu povolených klientov.", "last_rule_in_allowlist": "Nemôžete zakázať tohto klienta, pretože vylúčenie pravidla \"{{disallowed_rule}}\" zakáže zoznam \"povolených klientov\".", - "experimental": "Experimentálne", "use_saved_key": "Použiť predtým uložený kľúč", "parental_control": "Rodičovská kontrola", "safe_browsing": "Bezpečné prehliadanie", - "served_from_cache": "{{value}} (prevzatá z cache pamäte)" + "served_from_cache": "{{value}} (prevzatá z cache pamäte)", + "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov" } diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index a9c5fed6..1e89cc66 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -1,7 +1,7 @@ { "client_settings": "Nastavitve odjemalca", - "example_upstream_reserved": "Lahko določite gorvodni DNS <0>za določene domene", - "example_upstream_comment": "Lahko določite komentar", + "example_upstream_reserved": "gorvodni <0>za določene domene;", + "example_upstream_comment": "komentar.", "upstream_parallel": "Uporabite vzporedne zahteve za pospešitev reševanja s hkratnim poizvedovanjem vseh gorvodnih strežnikov.", "parallel_requests": "Vzporedne zahteve", "load_balancing": "Uravnavanje obremenitve", @@ -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", @@ -35,10 +35,10 @@ "dhcp_config_saved": "Nastavitve DHCP so bile uspešno shranjena", "dhcp_ipv4_settings": "Nastavitve DHCP IPv4", "dhcp_ipv6_settings": "Nastavitve DHCP IPv6", - "form_error_required": "Zahtevano polje", - "form_error_ip4_format": "Neveljaven naslov IPv4", - "form_error_ip4_range_start_format": "Neveljaven začetek oblike razpona IPv4", - "form_error_ip4_range_end_format": "Neveljaven konec oblike razpona IPv4", + "form_error_required": "Zahtevano polje.", + "form_error_ip4_format": "Neveljaven naslov IPv4.", + "form_error_ip4_range_start_format": "Neveljaven začetek razpona naslova IPv4", + "form_error_ip4_range_end_format": "Neveljaven konec razpona naslova IPv4", "form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda", "form_error_ip6_format": "Neveljaven naslov IPv6", "form_error_ip_format": "Neveljaven naslov IP", @@ -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", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "Ime gostitelja", "dhcp_table_expires": "Poteče", "dhcp_warning": "Če želite vseeno omogočiti strežnik DHCP, se prepričajte, da v vašem omrežju ni nobenega drugega aktivnega strežnika DHCP, saj lahko to prekine internetno povezljivost naprav v omrežju!", - "dhcp_error": "AdGuard Home ni mogel ugotoviti, ali je v omrežju še en aktivni strežnik DHCP.", + "dhcp_error": "AdGuard Home ni mogel ugotoviti, ali je v omrežju še en aktivni strežnik DHCP", "dhcp_static_ip_error": "Za uporabo strežnika DHCP mora biti nastavljen statični naslov IP. AdGuard Home ni uspel ugotoviti, ali je ta omrežni vmesnik nastavljen s statičnim naslovom IP. Prosimo, nastavite statični naslov IP ročno.", "dhcp_dynamic_ip_found": "Vaš sistem uporablja dinamično nastavitev naslova IP kartice <0>{{interfaceName}}. Za uporabo strežnika DHCP morate nastaviti statični naslov IP. Vaš trenutni naslov IP je<0>{{ipAddress}}. AdGuard Home bo samodejno nastavil ta naslov IP kot statičen, če pritisnete gumb 'Omogoči strežnik DHCP'.", "dhcp_lease_added": "Statičen najem \"{{key}}\" je uspešno dodan", @@ -143,7 +144,7 @@ "use_adguard_browsing_sec_hint": "AdGuard Home bo preveril ali je domena onemogočena s spletno storitivijo 'Varnost brskanja'ovoljenih. Za izvedbo preverjanja bo uporabil API za iskanje, ki je prijazen do zasebnosti: strežniku se pošlje le kratka predpona zgoščenke domenskega imena SHA256.", "use_adguard_parental": "Uporabi AdGuardovo spletno storitev 'Starševski nadzor'", "use_adguard_parental_hint": "AdGuard Home bo preveril, če domena vsebuje vsebine za odrasle. Uporablja enako, za zasebnost prijazen API, kot spletno storitev za varnost brskanja.", - "enforce_safe_search": "Uporabi varno iskanje", + "enforce_safe_search": "Uporabi Varno iskanje", "enforce_save_search_hint": "AdGuard Home bo vsilil varno iskanje v naslednjih iskalnikih: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.", "no_servers_specified": "Ni določenih strežnikov", "general_settings": "Splošne nastavitve", @@ -167,8 +168,8 @@ "enabled_safe_browsing_toast": "Omogočeno varno brskanje", "disabled_parental_toast": "Onemogočen starševski nadzor", "enabled_parental_toast": "Omogočen starševski nadzor", - "disabled_safe_search_toast": "Onemogočeno varno iskanje", - "enabled_save_search_toast": "Omogočeno varno iskanje", + "disabled_safe_search_toast": "Onemogočeno Varno iskanje", + "enabled_save_search_toast": "Omogočeno Varno iskanje", "enabled_table_header": "Omogočeno", "name_table_header": "Ime", "list_url_table_header": "Seznam URL naslovov", @@ -196,29 +197,32 @@ "choose_allowlist": "Izberite sezname dovoljenih", "enter_valid_blocklist": "Vnesite veljaven URL naslov seznama nedovoljenih.", "enter_valid_allowlist": "Vnesite veljaven URL naslov seznama dovoljenih.", - "form_error_url_format": "Neveljaven format URL naslova", + "form_error_url_format": "Neveljavna oblika URL naslova", "form_error_url_or_path_format": "Neveljaven URL ali absolutna pot seznama", "custom_filter_rules": "Pravila filtriranja po meri", "custom_filter_rules_hint": "V vrstico vnesite eno pravilo. Uporabite lahko pravila zaviranja oglasov ali sintakso gostiteljskih datotek.", "system_host_files": "Sistemske gostiteljske datooteke", "examples_title": "Primeri", - "example_meaning_filter_block": "onemogoči dostop do domene example.org in vseh njenih poddomen", - "example_meaning_filter_whitelist": "omogoči dostop do domene example.org in vseh njenih poddomen", - "example_meaning_host_block": "AdGuard Home bo zdaj vrnil naslov 127.0.0.1 za domeno example.org (ne pa tudi njunih poddomen).", - "example_comment": "! Tukaj je komentar", - "example_comment_meaning": "samo komentar", - "example_comment_hash": "# Tudi komentar", - "example_regex_meaning": "onemogoča dostop do domen, ki se ujemajo z določenim regularnim izrazom", - "example_upstream_regular": "redni DNS (nad UDP)", - "example_upstream_dot": "šifriran <0>DNS-prek-TLS", - "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS", - "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_meaning_filter_block": "onemogoči dostop do domene example.org in vseh njenih poddomen;", + "example_meaning_filter_whitelist": "omogoči dostop do domene example.org in vseh njenih poddomen;", + "example_meaning_host_block": "odgovori z 127.0.0.1 na primer.org (vendar ne za njegove poddomene);", + "example_comment": "! Tukaj je komentar.", + "example_comment_meaning": "samo komentar;", + "example_comment_hash": "# Tudi komentar.", + "example_regex_meaning": "onemogoča dostop do domen, ki se ujemajo z določenim regularnim izrazom.", + "example_upstream_regular": "redni DNS (nad UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Vsi seznami so že posodobljeni", "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", @@ -259,10 +263,10 @@ "query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje", "query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni", "anonymize_client_ip": "Anonimiziraj odjemalca IP", - "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih in statistiki", + "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki", "dns_config": "Konfiguracija strežnika DNS", "dns_cache_config": "Konfiguracija strežnika DNS", - "dns_cache_config_desc": "Tu lahko konfigurirate predpomnilnik DNS", + "dns_cache_config_desc": "Tu lahko nastavite predpomnilnik DNS", "blocking_mode": "Način zaviranja", "default": "Privzeto", "nxdomain": "NXDOMAIN", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-prek-QIUC", "client_id": "ID odjemalca", "client_id_placeholder": "Vnesite ID odjemalca", - "client_id_desc": "Različne odjemalce je mogoče prepoznati s posebnim ID-jem odjemalca. Tukaj lahko izveste več o prepoznavanju odjemalcev.", + "client_id_desc": "Odjemalce je mogoče identificirati s ClientID. Več o tem, kako prepoznati odjemalce, preberite tukaj.", "download_mobileconfig_doh": "Prenos .mobileconfig za DNS-preko-HTTPS", "download_mobileconfig_dot": "Prenos .mobileconfig za DNS-preko-TLS", "download_mobileconfig": "Prenesi nastavitveno datoteko", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Vnesite omejitev hitrosti", "rate_limit": "Omejitev hitrosti", "edns_enable": "Omogoči odjemalsko podomrežje EDNS", - "edns_cs_desc": "Pošlji podomrežja odjemalcev strežnikom DNS.", + "edns_cs_desc": "Dodaj možnost podomrežja odjemalca EDNS (ECS) zahtevam v gorvodnem toku in zabeleži vrednosti, ki jih pošljejo odjemalci, v dnevnik poizvedb.", "rate_limit_desc": "Dovoljeno število zahtev na sekundo na odjemalca. Nastavitev na 0 pomeni brez omejitve.", "blocking_ipv4_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo A", "blocking_ipv6_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo AAAA", @@ -334,12 +338,12 @@ "install_devices_router_list_4": "Pri nekaterih vrstah usmerjevalnikov strežnika DNS po meri ni mogoče nastaviti. V tem primeru vam lahko pomaga nastavitev AdGuard Home kot <0>strežnika DHCP. V nasprotnem primeru bi morali v priročniku usmerjevalnika preveriti, kako prilagodite strežnike DNS na vašem določenem modelu usmerjevalnika.", "install_devices_windows_list_1": "Odprite 'Nadzorno ploščo' prek menija 'Začetek' ali 'Iskanja v sistemu Windows'.", "install_devices_windows_list_2": "Pojdite v 'Omrežje' in 'Kategorija interneta' in nato v 'Omrežje' in 'Središče za skupno rabo'.", - "install_devices_windows_list_3": "Na levi strani zaslona poiščite 'Spremeni nastavitve kartice' in kliknite nanjo.", - "install_devices_windows_list_4": "Izberite aktivno povezavo, kliknite na njo z desno miškino tipko in izberite 'Lastnosti'.", + "install_devices_windows_list_3": "V levem podoknu kliknite 'Spremeni nastavitve kartice'\".", + "install_devices_windows_list_4": "Z desno tipko miške kliknite svojo aktivno povezavo in izberite Lastnosti.", "install_devices_windows_list_5": "Na seznamu poiščite 'Internet protokol različica 4 (TCP/IPv4)' (ali, za IPv6, 'Internet protokol različica 6 (TCP/IPv6)'), jo izberite in nato še enkrat kliknite 'Lastnosti'.", "install_devices_windows_list_6": "Izberite 'Uporabi naslednje naslove DNS strežnikov' in vnesite vaše naslove strežnika AdGuard Home.", - "install_devices_macos_list_1": "Kliknite ikono Apple in pojdite na 'Nastavitve sistema'.", - "install_devices_macos_list_2": "Kliknite na 'Omrežje'", + "install_devices_macos_list_1": "Kkliknite ikono Apple in pojdite na Sistemske nastavitve.", + "install_devices_macos_list_2": "Kliknite na 'Omrežje'.", "install_devices_macos_list_3": "Izberite prvo povezavo na seznamu in kliknite na 'Napredno'.", "install_devices_macos_list_4": "Izberite zavihek DNS in vnesite vaše naslove AdGuard Home strežnika.", "install_devices_android_list_1": "Na začetnem zaslonu menija Android tapnite 'Nastavitve'.", @@ -349,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", @@ -360,7 +364,7 @@ "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -388,8 +392,8 @@ "encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?", "topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja.", "topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja.", - "form_error_port_range": "Vnesite vrednost vrat v razponu med 80-65535", - "form_error_port_unsafe": "To so nevarna vrata", + "form_error_port_range": "Vnesite številko vrat v razponu med 80-65535", + "form_error_port_unsafe": "Nevarna vrata", "form_error_equal": "Ne sme biti enako", "form_error_password": "Geslo se ne ujema", "reset_settings": "Ponastavi nastavitve", @@ -397,7 +401,7 @@ "setup_guide": "Navodila za nastavitev", "dns_addresses": "DNS naslovi", "dns_start": "Zaganja se strežnik DNS", - "dns_status_error": "Napaka pri pridobivanju stanja strežnika DNS", + "dns_status_error": "Napaka pri preverjanju stanja strežnika DNS", "down": "Navzdol", "fix": "Popravi", "dns_providers": "Tukaj je <0>seznam znanih ponudnikov DNS, med katerimi lahko izbirate.", @@ -405,8 +409,8 @@ "update_failed": "Samodejna posodobitev ni uspela. Prosimo sledite korakom, da ročno posodobite.", "manual_update": "Za ročno posodobitev sledite tem korakom.", "processing_update": "Prosimo, počakajte. AdGuard Home se posodablja!", - "clients_title": "Odjemalci", - "clients_desc": "Konfigurirajte naprave, ki so povezane z AdGuard Home", + "clients_title": "Trajni odjemalci", + "clients_desc": "Nastavite trajne zapise odjemalca za povezane naprave z AdGuard Home", "settings_global": "Splošno", "settings_custom": "Po meri", "table_client": "Odjemalec", @@ -417,7 +421,7 @@ "client_edit": "Uredi odjemalca", "client_identifier": "Identifikator", "ip_address": "IP naslov", - "client_identifier_desc": "Odjemalce je mogoče prepoznati po naslovu IP, CIDR, naslovu MAC ali posebnem ID-ju odjemalca (lahko se uporablja za DoT/DoH/DoQ). <0>Tukaj lahko izveste več o prepoznavanju odjemalcev.", + "client_identifier_desc": "Odjemalce je mogoče prepoznati po naslovu IP, CIDR, naslovu MAC ali ID-ju (lahko se uporablja za DoT/DoH/DoQ). <0>Tukaj lahko izveste več o prepoznavanju odjemalcev.", "form_enter_ip": "Vnesite IP", "form_enter_subnet_ip": "V podomrežje \"{{cidr}}\" vnesite naslov IP", "form_enter_mac": "Vnesite MAC", @@ -432,18 +436,18 @@ "clients_not_found": "Odjemalcev ni bilo mogoče najti", "client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?", "list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?", - "auto_clients_title": "Odjemalci (čas izvajanja)", - "auto_clients_desc": "Podatki o odjemalcih, ki uporabljajo AdGuard Home, vendar niso shranjeni v konfiguraciji", + "auto_clients_title": "Odjemalci izvajanja", + "auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home", "access_title": "Nastavitve dostopa", - "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home.", + "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home", "access_allowed_title": "Dovoljeni odjemalci", - "access_allowed_desc": "Seznam naslovov CIDR ali IP. Če je nastavljen, bo AdGuard Home sprejel zahteve samo od teh teh naslovov IP.", + "access_allowed_desc": "Seznam CIDR-jev, naslovov IP ali ID-jev odjemalcev. Če ta seznam vsebuje vnose, bo AdGuard Home sprejel zahteve samo teh odjemalcev.", "access_disallowed_title": "Zavrnjeni odjemalci", - "access_disallowed_desc": "Seznam naslovov CIDR ali IP. Če je nastavljen, bo AdGuard Home spustil zahteve iz teh naslovov IP.", + "access_disallowed_desc": "Seznam CIDR-jev, naslovov IP ali ID-jev odjemalcev. Če ta seznam vsebuje vnose, bo AdGuard Home zavrnil zahteve teh odjemalcev. To polje je prezrto, če so vnosi v dovoljenih odjemalcih.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dan", "interval_days_plural": "{{count}} dni", "domain": "Domena", + "ecs": "ECS", "punycode": "Slaba koda", "answer": "Odgovor", "filter_added_successfully": "Seznam je bil uspešno dodan", @@ -532,7 +537,7 @@ "netname": "Ime omrežja", "network": "Omrežje", "descr": "Opis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Več o ustvarjanju lastnih seznamov gostiteljev.", "blocked_by_response": "Onemogočeno s CNAME ali IP v odgovoru", "blocked_by_cname_or_ip": "Onemogočeno s CNAME ali IP naslovom", @@ -552,10 +557,10 @@ "autofix_warning_list": "To bo izvedlo naslednja opravila: <0>Deaktiviraj sistemski DNSStubListener <0>Nastavi naslov strežnika DNS na 127.0.0.1 <0>Zamenjaj cilj simbolične povezave /etc/resolv.conf with /run/systemd/resolve/resolv.conf <0>Zaustavi DNSStubListener (znova naloži storitev systemd-resolved)", "autofix_warning_result": "Kot rezultat, bo vse zahteve DNS iz vašega sistema privzeto obdelal AdGuard Home.", "tags_title": "Oznake", - "tags_desc": "Izberete lahko oznake, ki ustrezajo odjemalcu. Oznake lahko vključite v pravila filtriranja in vam omogočajo, da jih natančneje uporabite. <0>Več o tem", + "tags_desc": "Izberete lahko oznake, ki ustrezajo odjemalcu. Oznake lahko vključite v pravila filtriranja in vam omogočajo, da jih natančneje uporabite. <0>Več o tem.", "form_select_tags": "Izberite odjemalske oznake", "check_title": "Preveri filtriranje", - "check_desc": "Preverite, ali je ime gostitelja filtrirano", + "check_desc": "Preverite, ali je ime gostitelja filtrirano.", "check": "Preveri", "form_enter_host": "Vnesite ime gostitelja", "filtered_custom_rules": "Filtrirano s pravili filtriranja po meri", @@ -598,15 +603,15 @@ "blocklist": "Seznam nedovoljenih", "milliseconds_abbreviation": "ms", "cache_size": "Velikost predpomnilnika", - "cache_size_desc": "Velikost predpomnilnika DNS (v bajtih)", + "cache_size_desc": "Velikost predpomnilnika DNS (v bajtih).", "cache_ttl_min_override": "Preglasi najmanjši TTL", "cache_ttl_max_override": "Preglasi največji TTL", "enter_cache_size": "Vnesite velikost predpomnilnika (v bajtih)", "enter_cache_ttl_min_override": "Vnesite najmanjši TTL (v sekundah)", "enter_cache_ttl_max_override": "Vnesite največji TTL (v sekundah)", - "cache_ttl_min_override_desc": "Razširite kratke vrednosti časa v živo (v sekundah), ki jih prejme strežnik za predpomnjenje, ko predpomni odzive DNS", - "cache_ttl_max_override_desc": "Nastavi največjo vrednost časa v živo (v sekundah) za vnose v predpomnilnik DNS", - "ttl_cache_validation": "Najmanjša vrednost predpomnilnika TTL mora biti manjša ali enaka največji vrednosti", + "cache_ttl_min_override_desc": "Podaljšajte kratke življenjske vrednosti (sekunde), prejete od gorvodnega strežnika pri predpomnjenju odgovorov DNS.", + "cache_ttl_max_override_desc": "Nastavite največjo vrednost življenjske dobe (sekunde) za vnose v predpomnilniku DNS.", + "ttl_cache_validation": "Najmanjša preglasitev TTL predpomnilnika mora biti manjša ali enaka najvišji", "cache_optimistic": "Optimistično predpomnjenje", "cache_optimistic_desc": "Poskrbi, da se AdGuard Home odzove iz predpomnilnika, tudi ko vnosi potečejo, in jih tudi poskusi osvežiti.", "filter_category_general": "Splošno", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home bo izpustil vse poizvedbe DNS iz tega odjemalca.", "filter_allowlist": "OPOZORILO: S to akcijo bo pravilo \"{{disallowed_rule}}\" izključeno s seznama dovoljenih odjemalcev.", "last_rule_in_allowlist": "Tega odjemalca ni mogoče onemogočiti, ker izključitev pravila \"{{disallowed_rule}}\" bo ONEMOGOČILO seznam 'Dovoljeni odjemalci'.", - "experimental": "Eksperimentalno", "use_saved_key": "Uporabi prej shranjeni ključ", "parental_control": "Starševski nadzor", "safe_browsing": "Varno brskanje", - "served_from_cache": "{{value}} (postreženo iz predpomnilnika)" + "served_from_cache": "{{value}} (postreženo iz predpomnilnika)", + "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov" } diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json index 59aad3a0..c42b57ad 100644 --- a/client/src/__locales/sr-cs.json +++ b/client/src/__locales/sr-cs.json @@ -1,10 +1,24 @@ { "client_settings": "Postavke klijenta", + "example_upstream_reserved": "upstream <0>za određene domene;", + "example_upstream_comment": "komentar.", + "upstream_parallel": "Koristite paralelne upite da biste ubrzali rešavanje tako što ćete istovremeno ispitati sve uzvodne servere.", "parallel_requests": "Paralelni zahtevi", "load_balancing": "Load-balancing", + "load_balancing_desc": "Koristi jedan upstream server. AdGuard Home koristi najnoviji nasumični algoritam da izabere server tako da se najbrži server češće koristi.", "bootstrap_dns": "Bootstrap DNS serveri", "bootstrap_dns_desc": "Bootstrap DNS serveri se koriste da reše IP adrese od DoH/DoT razrešivača koje ste odredili kao upstream.", + "local_ptr_title": "Private reverse DNS serveri", + "local_ptr_desc": "DNS serveri koje AdGuard Home koristi za lokalne PTR upite. Ovi serveri se koriste za rešavanje imena domaćina klijenata sa privatnim IP adresama, na primer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije podešen, AdGuard Home koristi adrese podrazumevanih DNS razrešivača vašeg OS-a osim adresa samog AdGuard Home-a.", + "local_ptr_default_resolver": "Podrazumevano, AdGuard Home koristi sledeće obrnute DNS razrešivače: {{ip}}.", + "local_ptr_no_default_resolver": "AdGuard Home ne može da odredi pogodne privatne obrnute DNS razrešivače za ovaj sistem.", + "local_ptr_placeholder": "Unesite jednu adresu servera po redu", + "resolve_clients_title": "Uključi obrnuto razrešavanje klijentskih IP adresa", + "resolve_clients_desc": "Obrnuto razrešite IP adrese klijenata u njihova imena domaćina slanjem PTR upita odgovarajućim razrešivačima (privatni DNS serveri za lokalne klijente, uzvodni serveri za klijente sa javnim IP adresama).", + "use_private_ptr_resolvers_title": "Koristi privatne obrnute razrešivače", + "use_private_ptr_resolvers_desc": "Izvršavanje obrnutih DNS izgleda za lokalno servirane adrese pomoću ovih uzvodnih servera. Ako je onemogućen, AdGuard Home odgovara sa NXDOMAIN na sve takve PTR zahteve osim klijenata poznatih iz DHCP- a, /etc/hosts itd.", "check_dhcp_servers": "Proveri DHCP servere", + "save_config": "Sačuvaj konfiguraciju", "enabled_dhcp": "DHCP server uključen", "disabled_dhcp": "DHCP server isključen", "unavailable_dhcp": "DHCP nije dostupan", @@ -13,19 +27,33 @@ "dhcp_description": "Ako vaš ruter nema DHCP postavke, možete koristiti AdGuard' ugrađen DHCP server.", "dhcp_enable": "Uključi DHCP server", "dhcp_disable": "Isključi DHCP server", + "dhcp_not_found": "Bezbedno je da uključite ugrađeni DHCP server. Nismo pronašli nijedan aktivan DHCP server na mreži. međutim, ohrabrujemo vas da to ponovo proverite ručno, jer naš automatski test trenutno nije 100% pouzdan.", "dhcp_found": "Pronađen je aktivan DHCP server na mreži. Nije bezbedno da uključite ugrađeni DHCP server.", "dhcp_leases": "DHCP pozajmljivanja", "dhcp_static_leases": "DHCP statička pozajmljivanja", "dhcp_leases_not_found": "DHCP pozajmljivanja nisu pronađena", + "dhcp_config_saved": "Sačuvaj DHCP konfiguraciju servera", "dhcp_ipv4_settings": "DHCP IPv4 postavke", "dhcp_ipv6_settings": "DHCP IPv6 postavke", "form_error_required": "Obavezno polje", - "form_error_ip4_format": "Nevažeći IPv4 format", - "form_error_ip6_format": "Nevažeći IPv6 format", - "form_error_ip_format": "Pogrešna IP adresa", - "form_error_mac_format": "Nevažeći MAC format", - "form_error_client_id_format": "Nevažeći format klijenta", + "form_error_ip4_format": "Nevažeća IPv4 adresa", + "form_error_ip4_range_start_format": "Nevažeća IPv4 addresa početnog opsega", + "form_error_ip4_range_end_format": "Nevažeća IPv4 addresa završnog opsega", + "form_error_ip4_gateway_format": "Nevažeća IPv4 addresa prozala", + "form_error_ip6_format": "Nevažeća IPv6 adresa", + "form_error_ip_format": "Nevažeća IP adresa", + "form_error_mac_format": "Nevažeća MAC adresa", + "form_error_client_id_format": "ClientID mora da sadrži samo brojeve, malim slovima i crticama", + "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", + "greater_range_end_error": "Mora biti veće od završnog opsega", + "subnet_error": "Asrese moraju biti u jednoj subnet", + "gateway_or_subnet_invalid": "Subnet mask nevažeća", "dhcp_form_gateway_input": "IP mrežnog prolaza", "dhcp_form_subnet_input": "Subnet mask", "dhcp_form_range_title": "Opseg IP adresa", @@ -39,11 +67,19 @@ "ip": "IP", "dhcp_table_hostname": "Ime hosta", "dhcp_table_expires": "Ističe", + "dhcp_warning": "Ako ipak želite da omogućite DHCP server, uverite se da u mreži ne postoji drugi aktivni DHCP server, jer to može da prekine Internet vezu za uređaje na mreži!", + "dhcp_error": "AdGuard Home nije mogao da utvrdi da li postoji još jedan aktivni DHCP server na mreži", + "dhcp_static_ip_error": "Da biste koristili DHCP server, morate postaviti statičnu IP adresu. AdGuard Home nije uspeo da utvrdi da li je ovaj mrežni interfejs konfigurisan pomoću statične IP adrese. Postavite statičnu IP adresu ručno.", + "dhcp_dynamic_ip_found": "Vaš sistem koristi dinamičku IP adresu za okruženje <0>{{interfaceName}}. Kako biste koristili DHCP server, morate podesiti statičku IP adresu. Vaša trenutna IP adresa je <0>{{ipAddress}}. Automatski ćemo podesiti ovu IP adresu kao statičku ako pritisnete Uključi DHCP dugme.", "dhcp_lease_added": "Statičko iznajmljivanje \"{{key}}\" uspešno dodato", "dhcp_lease_deleted": "Statičko iznajmljivanje lease \"{{key}}\" uspešno izbrisano", "dhcp_new_static_lease": "Novo statičko iznajmljivanje", "dhcp_static_leases_not_found": "Nisu pronađena statička DHCP iznajmljivanja", "dhcp_add_static_lease": "Dodaj statičko iznajmljivanje", + "dhcp_reset_leases": "Resetuj sva unajmljivanja", + "dhcp_reset_leases_confirm": "Jeste li sigurni da želite da resetujete sva pozajmljivanja?", + "dhcp_reset_leases_success": "DHCP unajmljivanja uspešno resetovana", + "dhcp_reset": "Jeste li sigurni da želite da resetujete DHCP konfiguraciju?", "country": "Zemlja", "city": "Grad", "delete_confirm": "Jeste li sigurni da želite da izbrišete \"{{key}}\"?", @@ -84,13 +120,22 @@ "for_last_24_hours": "u poslednja 24 časa", "for_last_days": "u poslednjih {{count}} dana", "for_last_days_plural": "u poslednjih {{count}} dana", + "stats_disabled": "Statistika je isključena. Možete ga uključiti sa stranice <0>sa postavkama.", + "stats_disabled_short": "Statistika je isključena", "no_domains_found": "Domeni nisu pronađeni", "requests_count": "Broj zahteva", "top_blocked_domains": "Najčešće blokirani domeni", "top_clients": "Najčešći klijenti", "no_clients_found": "Nema pronađenih klijenata", "general_statistics": "Opšte statistike", + "number_of_dns_query_days": "Broj obrađenih DNS unosa u poslednjih {{count}} dan", + "number_of_dns_query_days_plural": "Broj obrađenih DNS unosa u poslednjih {{count}} dana", + "number_of_dns_query_24_hours": "Broj obrađenih DNS unosa u poslednja 24 časa", + "number_of_dns_query_blocked_24_hours": "Broj DNS zahteva blokiranih od filtera blokatora reklama i blok liste hostova", + "number_of_dns_query_blocked_24_hours_by_sec": "Broj DNS zahteva blokiranih od AdGuard-ovog podprograma za bezbedno pregledanje", + "number_of_dns_query_blocked_24_hours_adult": "Broj blokiranih sajtova za odrasle", "enforced_save_search": "Nametni sigurno pretraživanje", + "number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje", "average_processing_time": "Prosečno vreme obrade", "average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva", "block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke", @@ -100,6 +145,7 @@ "use_adguard_parental": "Koristi AdGuard-ovu uslugu roditeljske kontrole", "use_adguard_parental_hint": "AdGuard Home će proveriti da li domen sadrži sadržaj za odrasle. Koristi se isti privatni prijateljski API kao i kod usluge bezbednog pregledanja.", "enforce_safe_search": "Nametni sigurno pretraživanje", + "enforce_save_search_hint": "AdGuard Home može nametnuti sigurno pretraživanje u sledećim pretraživačima: Google, Youtube, Bing, DuckDuckGo i Yandex.", "no_servers_specified": "Serveri nisu određeni", "general_settings": "Opšte postavke", "dns_settings": "DNS postavke", @@ -111,6 +157,7 @@ "encryption_settings": "Postavke šifrovanja", "dhcp_settings": "DHCP postavke", "upstream_dns": "Upstream DNS serveri", + "upstream_dns_help": "Unesite adrese servera, jednu po redu. Saznajte više o konfigurisanju upstream DNS servera.", "upstream_dns_configured_in_file": "Konfiguriši u {{path}}", "test_upstream_btn": "Testiraj upstreams", "upstreams": "Upstreams", @@ -150,27 +197,32 @@ "choose_allowlist": "Izaberite liste dozvoljenih", "enter_valid_blocklist": "Unesite važeći URL do blok liste.", "enter_valid_allowlist": "Unesite važeći URL do liste dozvoljenih.", - "form_error_url_format": "Nevažeći format Url-a", + "form_error_url_format": "Nevažeći format URL-a", "form_error_url_or_path_format": "URL ili apsolutna putanja do liste nije valjana", "custom_filter_rules": "Prilagođena pravila filtriranja", "custom_filter_rules_hint": "Unesite jedno pravilo po redu. Možete koristiti pravila blokatora reklama ili sintaksu hosts datoteke.", + "system_host_files": "System hosts datoteke", "examples_title": "Primeri", - "example_meaning_filter_block": "blokirajte pristup ka domenu primer.org i svim njegovim poddomenima", - "example_meaning_filter_whitelist": "dozvolite pristup ka domenu primer.org i svim njegovim poddomenima", - "example_meaning_host_block": "AdGuard Home će sada vratiti adresu 127.0.0.1 za domen primer.org (ali ne i za njegove poddomene).", - "example_comment": "! Ovde ide komentar", - "example_comment_meaning": "samo komentar", - "example_comment_hash": "# Takođe komentar", - "example_regex_meaning": "blokiranje pristupa domenima koji odgovaraju određenom uobičajenom izrazu", - "example_upstream_regular": "uobičajeno DNS (preko UDP)", - "example_upstream_dot": "šifrovano <0>DNS-over-TLS", - "example_upstream_doh": "šifrovano <0>DNS-over-HTTPS", - "example_upstream_doq": "šifrovano <0>DNS-over-QUIC", - "example_upstream_sdns": "možete koristiti <0>DNS brojeve za <1>DNSCrypt ili <2>DNS-over-HTTPS", - "example_upstream_tcp": "uobičajeni DNS (preko TCP)", + "example_meaning_filter_block": "blokirajte pristup ka primer.org i svim njegovim poddomenima;", + "example_meaning_filter_whitelist": "dozvolite pristup ka primer.org i svim njegovim poddomenima;", + "example_meaning_host_block": "vratiti adresu 127.0.0.1 za primer.org (ali ne i za njegove poddomene);", + "example_comment": "! Ovde ide komentar.", + "example_comment_meaning": "samo komentar;", + "example_comment_hash": "# Takođe komentar.", + "example_regex_meaning": "blokiranje pristupa domenima koji odgovaraju određenom uobičajenom izrazu.", + "example_upstream_regular": "uobičajeno DNS (preko UDP);", + "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;", + "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);", "all_lists_up_to_date_toast": "Sve liste su već ažurirane", + "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", @@ -195,7 +247,7 @@ "loading_table_status": "Učitavanje...", "page_table_footer_text": "Stranica", "rows_table_footer_text": "redovi", - "updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja", + "updated_custom_filtering_toast": "Prilagođena pravila su uspešno sačuvana", "rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja: {{rule}}", "rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja: {{rule}}", "query_log_response_status": "Stanje: {{value}}", @@ -227,13 +279,18 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", + "client_id": "ClientID", + "client_id_placeholder": "Unesite ClientID", + "client_id_desc": "Različiti klijenti mogu biti prepoznati posebnim ClientID. Ovde možete saznati više o tome kako da prepoznate klijente.", "download_mobileconfig_doh": "Preuzimanja", "download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS", + "download_mobileconfig": "Preuzmi konfiguracionu datoteku", "plain_dns": "Plain DNS", "form_enter_rate_limit": "Unesite ograničenje brzine", "rate_limit": "Ograničenje brzine", "edns_enable": "Uključi EDNS Client Subnet", - "edns_cs_desc": "Ako je uključeno, AdGuard Home će slati klijente na DNS servere.", + "edns_cs_desc": "Dodajte opciju podmreži EDNS klijenta (ECS) uzvodnim zahtevima i evidentirajte vrednosti koje klijenti šalju u evidenciji upita.", + "rate_limit_desc": "Broj zahteva u sekundi dozvoljen po klijentu. Postavljanje na 0 znači da nema ograničenja.", "blocking_ipv4_desc": "IP adresa koja će biti vraćena za blokirane zahteve", "blocking_ipv6_desc": "IP adresa koja će biti vraćena za blokirane AAAA zahteve", "blocking_mode_default": "Podrazumevano: Odgovara sa REFUSED kada je blokirano od Adblock-style pravila; odgovara sa IP adresom koja je određena u pravilu kada je blokiran od /etc/hosts-style pravila", @@ -256,6 +313,7 @@ "install_settings_listen": "Okruženje slušanja", "install_settings_port": "Port", "install_settings_interface_link": "Vaše AdGuard Home administratorsko web okruženje će biti dostupno na sledećim adresama:", + "form_error_port": "Unesite važeći broj porta", "install_settings_dns": "DNS server", "install_settings_dns_desc": "Potrebno je da konfigurišete vaše uređaje ili ruter da koristi DNS server sa sledećim adresama:", "install_settings_all_interfaces": "Sva okruženja", @@ -274,14 +332,16 @@ "install_devices_router": "Ruter", "install_devices_router_desc": "Ovo postavljanje će automatski pokriti sve uređaje koji su povezani na vaš kućni ruter pa nećete morati da konfigurišete svaki uređaj posebno.", "install_devices_address": "AdGuard Home DNS server sluša na sledećim adresama", + "install_devices_router_list_1": "Otvorite željene postavke mrežne skretnice. Obično mu možete pristupiti iz pregledača putem URL adrese, kao što su http://192.168.0.1/ ili http://192.168.1.1/. Od vas će možda biti zatraženo da unesete lozinku. Ako je se ne sećate, često možete da poništite lozinku pritiskom na dugme na samoj mrežnoj skretnici, ali imajte na umu da ćete, ako se ova procedura izabere, verovatno izgubiti celu konfiguraciju rutera. Ako ruter zahteva aplikaciju za podešavanje, instalirajte aplikaciju na telefonu ili računaru i koristite je za pristup postavkama rutera.", "install_devices_router_list_2": "Pronađite DHCP ili DNS postavke. Potražite DNS slova pored polja koje dozvoljava dve ili tri skupine brojeva, a svaka može da sadrži četiri grupe od jedne do tri cifre.", "install_devices_router_list_3": "Tamo unesite adrese AdGuard home servera.", + "install_devices_router_list_4": "Na nekim tipovima mrežnih skretnica nije moguće podesiti prilagođeni DNS server. U tom slučaju, podešavanje AdGuard Home-a kao <0>DHCP servera može da pomogne. U suprotnom, trebalo bi da proverite uputstvo mrežne skretnice o prilagođavanju DNS servera na određenom modelu rutera.", "install_devices_windows_list_1": "Otvorite kontrolnu tablu iz startnog menija ili kroz Windows pretragu.", "install_devices_windows_list_2": "Otvorite kategoriju mreža i internet a onda otiđite u centar za mrežu i deljenje.", - "install_devices_windows_list_3": "Na levoj strani ekrana pronađite Promena postavke adaptera i kliknite tu.", - "install_devices_windows_list_4": "Izaberite vašu aktivnu vezu, desnim tasterom kliknite na nju i izaberite Svojstva.", - "install_devices_windows_list_5": "Na listi pronađite Internet Protokol verzija 4 (TCP/IP), izaberite ga pa kliknite ponovo na Svojstva.", - "install_devices_windows_list_6": "Izaberite Koristi sledeće adrese DNS servera pa unesite vaše adrese AdGuard Home servera.", + "install_devices_windows_list_3": "Na levoj tabli kliknite na dugme \"Promeni postavke adaptera\".", + "install_devices_windows_list_4": "Kliknite desnim tasterom miša na aktivnu vezu i izaberite stavku Svojstva.", + "install_devices_windows_list_5": "Na listi pronađite Internet Protokol verzija 4 (TCP/IP) (ili, za IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), izaberite ga pa kliknite ponovo na Svojstva.", + "install_devices_windows_list_6": "Izaberite \"Koristi sledeće adrese DNS servera\" pa unesite vaše adrese AdGuard Home servera.", "install_devices_macos_list_1": "Kliknite na ikonicu jabuke pa otiđite na postavke sistema.", "install_devices_macos_list_2": "Kliknite na mrežu.", "install_devices_macos_list_3": "Izaberite prvu vezu sa liste pa kliknite na više opcija.", @@ -300,9 +360,11 @@ "open_dashboard": "Otvori kontrolnu tablu", "install_saved": "Uspešno sačuvano", "encryption_title": "Šifrovanje", - "encryption_desc": "Šifrovanje (HTTPS/TLS) podrška za oba DNS i administratorsko okruženje", + "encryption_desc": "Šifrovanje (HTTPS/QUIC/TLS) podrška za oba DNS i administratorsko okruženje", + "encryption_config_saved": "Konfiguracija šifrovanja je sačuvana", "encryption_server": "Ime servera", "encryption_server_enter": "Unesite vaše ime domena", + "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", @@ -310,7 +372,7 @@ "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", - "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_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.", @@ -330,21 +392,25 @@ "encryption_reset": "Jeste li sigurni da želite dda resetujete postavke šifrovanja?", "topline_expiring_certificate": "Vaš SSL sertifikat uskoro ističe. Ažurirajte <0>postavke šifrovanja.", "topline_expired_certificate": "Vaš SSL sertifikat je istekao. Ažurirajte <0>postavke šifrovanja.", - "form_error_port_unsafe": "Ovo nije siguran port", + "form_error_port_range": "Unesite vrednost porta u opsegu od 80-65535", + "form_error_port_unsafe": "Nije siguran port", + "form_error_equal": "Ne smije biti jednako", "form_error_password": "Lozinke se ne podudaraju", "reset_settings": "Vrati postavke na podrazumevano", "update_announcement": "AdGuard Home {{version}} je sada dostupan! <0>Kliknite ovde za više informacija.", + "setup_guide": "Uputstvo za podešavanje", "dns_addresses": "DNS adrese", "dns_start": "DNS server se pokreće", - "dns_status_error": "Greška prilikom pribavljanja stanja DNS servera", + "dns_status_error": "Greška pri proveri statusa DNS servera", "down": "Dole", "fix": "Popravi", "dns_providers": "Ovo je a <0>lista poznatih DNS dobavljača sa koje možete da izaberete.", "update_now": "Ažuriraj sada", "update_failed": "Automatsko ažuriranje nije uspelo. Molimo vas pratite korake za ručno ažuriranje.", + "manual_update": "Molimo vas pratite korake za ručno ažuriranje.", "processing_update": "Molimo sačekajte. AdGuard Home se ažurira", - "clients_title": "Klijenti", - "clients_desc": "Konfigurišite uređaje koji su povezani na AdGuard Home", + "clients_title": "Uporni klijenti", + "clients_desc": "Konfigurisanje stalnih klijenata za uređaje povezane sa AdGuard Home", "settings_global": "Globalno", "settings_custom": "Prilagođeno", "table_client": "Klijent", @@ -355,7 +421,9 @@ "client_edit": "Izmeni klijent", "client_identifier": "Identifikator", "ip_address": "IP adresa", + "client_identifier_desc": "Klijenti se mogu identifikovati po IP adresi, CIDR-u, MAC adresi ili ClientID (može se koristiti za DoT/DoH/DoQ). Saznajte više o tome kako <0>da identifikujete klijente.", "form_enter_ip": "Unesite IP", + "form_enter_subnet_ip": "Unesite IP adresu subnet \"{{cidr}}\"", "form_enter_mac": "Unesite MAC", "form_enter_id": "Unesite identifikator", "form_add_id": "Dodaj identifikator", @@ -369,16 +437,17 @@ "client_confirm_delete": "Jeste li sigurni da želite da izbrišete klijenta \"{{key}}\"?", "list_confirm_delete": "Jeste li sigurni da želite da izbrišete ovu listu?", "auto_clients_title": "Klijenti (runtime)", - "auto_clients_desc": "Podaci o klijentima koji koriste AdGuard Home, ali nisu sačuvani u konfiguraciji", + "auto_clients_desc": "Uređaji koji nisu na listi upornih klijenata koji i dalje mogu da koriste AdGuard Home", "access_title": "Postavke pristupa", - "access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard home DNS server.", + "access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard Home DNS server", "access_allowed_title": "Dozvoljeni klijenti", - "access_allowed_desc": "Spisak CIDR ili IP adresa. Ako je podešeno, AdGuard Home će prihvatiti zahteve samo od ovih IP adresa.", + "access_allowed_desc": "Spisak CIDR, IP adresa ili ClientIDs. Ako ova lista ima stavke, AdGuard Home će prihvatiti zahteve samo ovih klijenata.", "access_disallowed_title": "Zabranjeni klijenti", - "access_disallowed_desc": "Lista CIDR ili IP adresa.. Ako je podešeno, AdGuard Home će odbijati zahteve od ovih IP adresa.", + "access_disallowed_desc": "Spisak CIDR, IP adresa ili ClientIDs. Ako ova lista ima stavke, AdGuard Home će otpustiti zahteve ovih klijenata. Ovo polje se zanemaruje ako postoje stavke u dozvoljenim klijentima.", "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", @@ -397,6 +466,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Više implementacija ćete pronaći <0>ovde i <1>ovde.", + "setup_dns_privacy_ioc_mac": "iOS i macOS konfiguracija", "setup_dns_notice": "Kako biste koristili <1>DNS-over-HTTPS ili <1>DNS-over-TLS, potrebno je da <0>konfigurišete šifrovanje u AdGuard Home postavkama.", "rewrite_added": "DNS prepisivanje za \"{{key}}\" je uspešno dodato", "rewrite_deleted": "DNS prepisivanje za \"{{key}}\" uspešno izbrisano", @@ -429,11 +499,13 @@ "encryption_key_source_content": "Nalepi sadržaj privatnog ključa", "stats_params": "Konfiguracija statistike", "config_successfully_saved": "Konfiguracija je uspešno sačuvana", - "interval_6_hour": "6 sati", + "interval_6_hour": "6 časa", "interval_24_hour": "24 časa", "interval_days": "{{count}} dan", "interval_days_plural": "{{count}} dana", "domain": "Domen", + "ecs": "ECS", + "punycode": "Punycode", "answer": "Odgovor", "filter_added_successfully": "Filter je uspešno dodat", "filter_removed_successfully": "Lista je uspešno uklonjena", @@ -445,6 +517,7 @@ "statistics_clear_confirm": "Jeste li sigurni da želite da očistite statistiku?", "statistics_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje statistike? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni", "statistics_cleared": "Statistika je uspešno očišćena", + "statistics_enable": "Uključi statistiku", "interval_hours": "{{count}} čas", "interval_hours_plural": "{{count}} časova", "filters_configuration": "Konfiguracija filtera", @@ -464,7 +537,7 @@ "netname": "Ime mreže", "network": "Mreža", "descr": "Opis", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Saznajte više o stvaranju vaše lične blokliste hostova.", "blocked_by_response": "Blokirano od CNAME ili IP u odgovoru", "blocked_by_cname_or_ip": "Blokirano od CNAME ili IP", @@ -476,17 +549,18 @@ "rewrite_domain_name": "Ime domena: dodajte CNAME zapis", "rewrite_A": "<0>A: posebna vrednost, zadrži <0>A records iz apstrima", "rewrite_AAAA": "<0>AAAA: posebna vrednost, zadržite <0>AAAA records iz apstrima", - "disable_ipv6": "Isključi IPv6", - "disable_ipv6_desc": "Ako je ovo uključeno, svi DNS unosi za IPv6 adrese (type AAAA) će biti odbačeni.", + "disable_ipv6": "Onemogući rešavanje IPv6 adresa", + "disable_ipv6_desc": "Otpustite sve DNS upite za IPv6 adrese (otkucajte AAAA).", "fastest_addr": "Najbrža IP adresa", + "fastest_addr_desc": "Pretražuje sve DNS servere i vraća najbržu IP adresu među svim odgovorima. Ovo će usporiti DNS pretragu jer moramo da čekamo na odgovore od svih DNS servera, ali će poboljšati sveukupnu povezanost.", "autofix_warning_text": "Ako kliknete \"Popravi\", AdGuardHome će konfigurisati vaš sistem da koristi AdGuardHome DNS server.", "autofix_warning_list": "To će izvršiti sledeće zadatke: <0>Deaktiviranje system DNSStubListener <0>Set DNS server address to 127.0.0.1 <0>Replace symbolic link target of /etc/resolv.conf to /run/systemd/resolve/resolv.conf <0>Stop DNSStubListener (reload systemd-resolved service)", "autofix_warning_result": "Kao rezultat, svi DNS zahtevi sa vašeg sistema će biti obrađeni od AdGuardHome.", "tags_title": "Oznake", - "tags_desc": "Možete izabrati oznake koje odgovaraju klijentu. Oznake mogu biti uključene u pravila filtriranja i dozvoljavaju vam da ih preciznije primenite. <0>Saznajte više", + "tags_desc": "Možete izabrati oznake koje odgovaraju klijentu. Uključite oznake u pravila filtriranja da biste ih preciznije primenili. <0>Saznajte više.", "form_select_tags": "Izaberite oznake klijenta", "check_title": "Proverite filtriranje", - "check_desc": "Proverite da li je host filtriran", + "check_desc": "Proverite da li je host filtriran.", "check": "Proveri", "form_enter_host": "Unesite host", "filtered_custom_rules": "Filtrirano od strane prilagođenog pravila", @@ -508,18 +582,19 @@ "set_static_ip": "Postavite statičku IP adresu", "install_static_ok": "Dobre vesti! Statička IP adresa je već konfigurisana", "install_static_error": "AdGuard Home se ne može automatski konfigurisati za ovo mrežno okruženje. Pogledajte uputstvo kako da to ručno uradite.", + "install_static_configure": "Otkrili smo da se koristi dinamička IP adresa — <0>{{ip}}. Želite li da je koristite kao vašu statičku adresu?", "confirm_static_ip": "AdGuard Home će konfigurisati {{ip}} da bude vaša statička IP adresa. Želite li da nastavite?", "list_updated": "{{count}} lista ažurirana", "list_updated_plural": "{{count}} lista ažurirano", "dnssec_enable": "Uključi DNSSEC", - "dnssec_enable_desc": "Postavlja DNSSEC zastavicu u odlaznim DNS zahtevima i proverava rezultat (DNSSEC rešavač je potreban)", + "dnssec_enable_desc": "Postavlja DNSSEC zastavicu u odlaznim DNS zahtevima i proverava rezultat (DNSSEC rešavač je potreban).", "validated_with_dnssec": "Potvrđeno sa DNSSEC", "all_queries": "Svi zahtevi", "show_blocked_responses": "Blokirano", "show_whitelisted_responses": "Na beloj listi", "show_processed_responses": "Obrađeno", "blocked_safebrowsing": "Blokiralo bezbedno pregledanje", - "blocked_adult_websites": "Blokirala roditeljska kontrola", + "blocked_adult_websites": "Blokiraj sajtove za odrasle", "blocked_threats": "Blokiranih pretnji", "allowed": "Dozvoljeno", "filtered": "Filtrirano", @@ -528,15 +603,17 @@ "blocklist": "Lista blokiranih", "milliseconds_abbreviation": "ms", "cache_size": "Veličina predmemorije", - "cache_size_desc": "Veličina DNS predmemorije (u bitovima)", + "cache_size_desc": "Veličina DNS predmemorije (u bitovima).", "cache_ttl_min_override": "Prepiši najmanji TTL", "cache_ttl_max_override": "Prepiši najveći TTL", "enter_cache_size": "Unesite veličinu predmemorije", "enter_cache_ttl_min_override": "Unesite najmanji TTL", "enter_cache_ttl_max_override": "Unesite najveći TTL", - "cache_ttl_min_override_desc": "Prepiši TTL vrednost (minimum) dobijen od apstrim servera. Ova vrednost ne može biti veća od 3600 (1 sat)", - "cache_ttl_max_override_desc": "Prepiši TTL vrednost (maksimum) dobijen od apstrim servera", + "cache_ttl_min_override_desc": "Proširivanje kratkih vrednosti vremena na život (sekundi) primljenih sa uzvodnog servera prilikom keširanje DNS odgovora.", + "cache_ttl_max_override_desc": "Prepiši TTL vrednost (maksimum) dobijen od apstrim servera.", "ttl_cache_validation": "Minimalna TTL vrednost mora biti manja ili jednaka najvišij vrednosti", + "cache_optimistic": "Optimistično keširanje", + "cache_optimistic_desc": "Neka AdGuard Home odgovara iz predmemorije čak i kada su unosi istekli pa pokušaj da ih osvežiš.", "filter_category_general": "Opšte", "filter_category_security": "Bezbednost", "filter_category_regional": "Region", @@ -545,10 +622,16 @@ "filter_category_security_desc": "Lista specijalizovana za blokiranje štetnog softvera, štetnih i fišing domena", "filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja", "filter_category_other_desc": "Ostale liste blokiranja", + "setup_config_to_enable_dhcp_server": "Podesite konfiguraciju kako biste omogućili DHCP server", "original_response": "Izvorni odgovor", "click_to_view_queries": "Kliknite da pogledate zahteve", "port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo kako da to rešite.", "adg_will_drop_dns_queries": "AdGuard Home će odbacivati sve DNS unose od ovog klijenta.", - "experimental": "Eksperimentalno", - "parental_control": "Roditeljska kontrola" + "filter_allowlist": "UPOZORENJE: Ova radnja će takođe izuzeti pravilo \"{{disallowed_rule}}\" sa the spiska dozvoljenih klijenata.", + "last_rule_in_allowlist": "Ne mogu da zabranim ovog klijenta zato što će izuzimanje pravila \"{{disallowed_rule}}\" onemogućiti \"dozvoljene klijente\".", + "use_saved_key": "Koristi prethodno sačuvan ključ", + "parental_control": "Roditeljska kontrola", + "safe_browsing": "Sigurno pregledanje", + "served_from_cache": "{{value}} (posluženo iz predmemorije)", + "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova" } diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json index 195a651f..5fc3a4cc 100644 --- a/client/src/__locales/sv.json +++ b/client/src/__locales/sv.json @@ -1,7 +1,7 @@ { "client_settings": "Klientinställningar", - "example_upstream_reserved": "Du kan specificera DNS-uppström <0>för en specifik domän", - "example_upstream_comment": "Du kan ange en kommentar", + "example_upstream_reserved": "uppström <0>för en specifik domän;", + "example_upstream_comment": "du kan ange en kommentar.", "upstream_parallel": "Använd parallella förfrågningar för att snabba upp dessa genom att fråga alla uppströmsservrar samtidigt.", "parallel_requests": "Parallella förfrågningar", "load_balancing": "Lastbalansering", @@ -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", @@ -94,8 +95,8 @@ "filters": "Filter", "filter": "Filter", "query_log": "Förfrågningslogg", - "compact": "Komprimera", - "nothing_found": "Inget hittades", + "compact": "Kompakt", + "nothing_found": "Ingenting hittades", "faq": "FAQ", "version": "version", "address": "Adress", @@ -196,7 +197,7 @@ "choose_allowlist": "Välj frilistor", "enter_valid_blocklist": "Ange en giltig URL till blockeringslistan.", "enter_valid_allowlist": "Ange en giltig URL till frilistan.", - "form_error_url_format": "Ogiltigt URL format", + "form_error_url_format": "Ogiltigt URL-format", "form_error_url_or_path_format": "Ogiltig URL eller absolut sökväg till listan", "custom_filter_rules": "Egna filterregler", "custom_filter_rules_hint": "Skriv en regel per rad. Du kan använda antingen annonsblockeringsregler eller värdfilssyntax.", @@ -210,15 +211,18 @@ "example_comment_hash": "# Också en kommentar", "example_regex_meaning": "blockera åtkomst till domäner som matchar det angivna uttrycket", "example_upstream_regular": "vanlig DNS (över UDP)", + "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", + "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);", "all_lists_up_to_date_toast": "Alla listor är redan uppdaterade", "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", @@ -360,7 +364,7 @@ "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", @@ -368,7 +372,7 @@ "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", - "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_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.", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "Ditt SSL-certifikat håller på att gå ut. <0>Krypteringsinställningar.", "topline_expired_certificate": "Ditt SSL-certifikat har gått ut. Uppdatera <0>Krypteringsinställningar-", "form_error_port_range": "Ange ett portnummer inom värdena 80-65535", - "form_error_port_unsafe": "Det här är en osäker port", + "form_error_port_unsafe": "Osäker port", "form_error_equal": "Får inte vara samma", "form_error_password": "Lösenorden överensstämmer inte", "reset_settings": "Återställ inställningar", @@ -405,8 +409,8 @@ "update_failed": "Automatisk uppdatering misslyckad. Var god följ stegen för att uppdatera manuellt.", "manual_update": "Vänligen följ dessa steg för att uppdatera manuellt.", "processing_update": "Vänta, AdGuard Home uppdateras", - "clients_title": "Klienter", - "clients_desc": "Konfigurera enheter uppkopplade mot AdGuard Home", + "clients_title": "Uthålliga klienter", + "clients_desc": "Konfigurera beständiga klientposter för enheter som är anslutna till AdGuard Home", "settings_global": "Global", "settings_custom": "Anpassade", "table_client": "Klient", @@ -417,7 +421,7 @@ "client_edit": "Redigera klient", "client_identifier": "Identifikator", "ip_address": "IP-adress", - "client_identifier_desc": "Klienter kan identifieras med IP-adressen, CIDR, MAC-adressen eller ett speciellt klient-ID (kan användas för DoT/DoH/DoQ). <0>Här kan du lära dig mer om hur du identifierar klienter.", + "client_identifier_desc": "Klienter kan identifieras med IP-adressen, CIDR, MAC-adressen eller ett ClientID (kan användas för DoT/DoH/DoQ). <0>Här kan du lära dig mer om hur du identifierar klienter.", "form_enter_ip": "Skriv in IP", "form_enter_subnet_ip": "Ange en IP adress i subnätet \"{{cidr}}\"", "form_enter_mac": "Skriv in MAC", @@ -433,17 +437,17 @@ "client_confirm_delete": "Är du säker på att du vill ta bort klient \"{{key}}\"?", "list_confirm_delete": "Är du säker på att du vill ta bort den här listan?", "auto_clients_title": "Klienter (körtid)", - "auto_clients_desc": "Data från klienter som använder AdGuard Home, men inte är sparade i konfigurationen", + "auto_clients_desc": "Enheter som inte finns på listan över beständiga klienter som fortfarande kan använda AdGuard Home", "access_title": "Åtkomstinställningar", - "access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server.", + "access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server", "access_allowed_title": "Tillåtna klienter", - "access_allowed_desc": "En lista över CIDR, IP-adresser eller klient-ID. Om det är konfigurerat accepterar AdGuard Home endast förfrågningar från dessa klienter.", + "access_allowed_desc": "En lista över CIDR, IP-adresser eller ClientID. Om den här listan har poster accepterar AdGuard Home endast förfrågningar från dessa clienter.", "access_disallowed_title": "Otillåtna klienter", - "access_disallowed_desc": "En lista över CIDR, IP-adresser eller klient-ID. Om det är konfigurerat kommer AdGuard Home att kasta förfrågningar från dessa klienter. Om tillåtna klienter är konfigurerade ignoreras detta fält.", + "access_disallowed_desc": "En lista över CIDR, IP-adresser eller ClientID. Om den här listan har poster kommer AdGuard Home att ta bort förfrågningar från dessa klienter. Detta fält ignoreras om det finns poster i Tillåtna klienter.", "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", @@ -500,6 +504,7 @@ "interval_days": "{{count}} dag", "interval_days_plural": "{{count}} dagar", "domain": "Domän", + "ecs": "ECS", "punycode": "Punycode", "answer": "Svar", "filter_added_successfully": "Listan har lagts till", @@ -532,7 +537,7 @@ "netname": "Nätverksnamn", "network": "Nätverk", "descr": "Beskrivning", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Mer info om att skapa dina egna blockeringslistor för värdar.", "blocked_by_response": "Blockerad av CNAME eller IP i svaret", "blocked_by_cname_or_ip": "Blockerad av CNAME eller IP", @@ -552,17 +557,17 @@ "autofix_warning_list": "Den kommer att utföra följande uppgifter: <0>Avaktivera system DNSStubListener <0>Sätt DNS serveradress till 127.0.0.1 <0>Ersätt symboliskt länkmål för /etc/resolv.conf med /run/systemd /resolve/resolv.conf <0>Stoppa DNSStubListener (ladda om systemd-resolved tjänst)", "autofix_warning_result": "Som ett resultat kommer alla DNS-förfrågningar från ditt system att behandlas av AdGuard Home som standard.", "tags_title": "Taggar", - "tags_desc": "Du kan välja de taggar som motsvarar klienten. Taggar kan inkluderas i filtreringsreglerna och låter dig tillämpa dem mer exakt. <0>Läs mer", + "tags_desc": "Du kan välja de taggar som motsvarar klienten. Inkludera taggar i filtreringsregler för att tillämpa dem mer exakt. <0>Läs mer.", "form_select_tags": "Välj klienttaggar", "check_title": "Kontrollera filtreringen", - "check_desc": "Kontrollera om värdnamnet är filtrerat", + "check_desc": "Kontrollera om värdnamnet är filtrerat.", "check": "Kontrollera", "form_enter_host": "Ange ett värdnamn", "filtered_custom_rules": "Filtrerat efter anpassade filtreringsregler", "choose_from_list": "Välj från listan", "add_custom_list": "Lägg till en anpassad lista", "host_whitelisted": "Värden är tillåten", - "check_ip": "IP adresser: {{ip}}", + "check_ip": "IP-adresser: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Anledning: {{reason}}", "check_service": "Service namn: {{service}}", @@ -594,22 +599,22 @@ "allowed": "Vitlistade", "filtered": "Filtrerad", "rewritten": "Omskriven", - "safe_search": "Säker surf", + "safe_search": "Säker sökning", "blocklist": "Blocklista", "milliseconds_abbreviation": "ms", "cache_size": "Cachestorlek", - "cache_size_desc": "DNS cachestorlek (i byte)", + "cache_size_desc": "DNS cachestorlek (i byte).", "cache_ttl_min_override": "Åsidosätt minsta TTL", "cache_ttl_max_override": "Åsidosätt maximal TTL", "enter_cache_size": "Ange cachestorlek (byte)", "enter_cache_ttl_min_override": "Ange minsta TTL (sekunder)", "enter_cache_ttl_max_override": "Ange maximal TTL (sekunder)", - "cache_ttl_min_override_desc": "Förläng värden för korta time-to-live värden (sekunder) som tas emot från uppströms server när DNS svar cachelagras", - "cache_ttl_max_override_desc": "Ställ in ett maximalt värde för time-to-live (sekunder) för poster i DNS cachen", + "cache_ttl_min_override_desc": "Förläng värden för korta time-to-live värden (sekunder) som tas emot från uppströms server när DNS svar cachelagras.", + "cache_ttl_max_override_desc": "Ställ in ett maximalt värde för time-to-live (sekunder) för poster i DNS cachen.", "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", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home kommer att kasta alla DNS-frågor från den här klienten.", "filter_allowlist": "VARNING: Denna åtgärd kommer också att utesluta regeln \"{{disallowed_rule}}\" från listan över tillåtna klienter.", "last_rule_in_allowlist": "Det går inte att avvisa den här klienten eftersom att utesluta regeln \"{{disallowed_rule}}\" kommer att INAKTIVERA listan \"Tillåtna klienter\".", - "experimental": "Experimentell", "use_saved_key": "Använd den tidigare sparade nyckeln", "parental_control": "Föräldrakontroll", "safe_browsing": "Säker surfning", - "served_from_cache": "{{value}} (levereras från cache)" + "served_from_cache": "{{value}} (levereras från cache)", + "form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt" } diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index bf0431f7..e4415e33 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -1,7 +1,7 @@ { "client_settings": "İstemci ayarları", - "example_upstream_reserved": "<0>Belirli alan adları için DNS üst sunucusu tanımlayabilirsiniz.", - "example_upstream_comment": "Bir yorum belirtebilirsiniz", + "example_upstream_reserved": "<0>belirli alan adları için bir üst sunucusu;", + "example_upstream_comment": "bir yorum.", "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", "parallel_requests": "Paralel istekler", "load_balancing": "Yük dengeleme", @@ -9,12 +9,12 @@ "bootstrap_dns": "DNS Önyükleme sunucuları", "bootstrap_dns_desc": "DNS Önyükleme sunucuları, belirttiğiniz üst sunucuların DoH/DoT çözümleyicilerine ait IP adreslerinin çözümlemek için kullanılır.", "local_ptr_title": "Özel ters DNS sunucuları", - "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak \"192.168.12.34\" gibi özel IP adreslerine sahip istemcilerin ana bilgisayar adlarını çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.", + "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak, örneğin \"192.168.12.34\" gibi özel IP aralıklarındaki adresler için PTR isteklerini çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.", "local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.", "local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.", "local_ptr_placeholder": "Her satıra bir sunucu adresi girin", "resolve_clients_title": "İstemcilerin IP adreslerinin ters çözümlenmesini etkinleştir", - "resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana bilgisayar adlarının tersine çözün.", + "resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözün.", "use_private_ptr_resolvers_title": "Özel ters DNS çözümleyicileri kullan", "use_private_ptr_resolvers_desc": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS aramaları yapın. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts, vb. tarafından bilinen istemciler dışında bu tür tüm PTR isteklerine NXDOMAIN ile yanıt verir.", "check_dhcp_servers": "DHCP sunucularını denetle", @@ -36,23 +36,24 @@ "dhcp_ipv4_settings": "DHCP IPv4 Ayarları", "dhcp_ipv6_settings": "DHCP IPv6 Ayarları", "form_error_required": "Gerekli alan", - "form_error_ip4_format": "IPv4 adresi geçersiz", - "form_error_ip4_range_start_format": "Başlangıç aralığı IPv4 adresi geçersiz", - "form_error_ip4_range_end_format": "Bitiş aralığı IPv4 adresi geçersiz", - "form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz", - "form_error_ip6_format": "IPv6 adresi geçersiz", - "form_error_ip_format": "IP adresi geçersiz", - "form_error_mac_format": "MAC adresi geçersiz", - "form_error_client_id_format": "İstemci kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir", - "form_error_server_name": "Sunucu adı geçersiz", + "form_error_ip4_format": "Geçersiz IPv4 adresi", + "form_error_ip4_range_start_format": "Geçersiz başlangıç aralığı IPv4 biçimi", + "form_error_ip4_range_end_format": "Geçersiz bitiş aralığı IPv4 adresi", + "form_error_ip4_gateway_format": "Geçersiz ağ geçidi IPv4 adresi", + "form_error_ip6_format": "Geçersiz IPv6 adresi", + "form_error_ip_format": "Geçersiz IP adresi", + "form_error_mac_format": "Geçersiz MAC adresi", + "form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir", + "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", "greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır", "subnet_error": "Adresler bir alt ağda olmalıdır", - "gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz", + "gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz.", "dhcp_form_gateway_input": "Ağ geçidi IP", "dhcp_form_subnet_input": "Alt ağ maskesi", "dhcp_form_range_title": "IP adresi aralığı", @@ -64,10 +65,10 @@ "dhcp_hardware_address": "Donanım adresi", "dhcp_ip_addresses": "IP adresleri", "ip": "IP", - "dhcp_table_hostname": "Bilgisayar Adı", + "dhcp_table_hostname": "Ana makine Adı", "dhcp_table_expires": "Bitiş tarihi", "dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların İnternet bağlantısı kesilebilir!", - "dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi.", + "dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi", "dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.", "dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}} arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.", "dhcp_lease_added": "Sabit kiralama \"{{key}}\" başarıyla eklendi", @@ -82,7 +83,7 @@ "country": "Ülke", "city": "Şehir", "delete_confirm": "\"{{key}}\" öğesini silmek istediğinizden emin misiniz?", - "form_enter_hostname": "Ana bilgisayar adı girin", + "form_enter_hostname": "Ana makine adı girin", "error_details": "Hata ayrıntıları", "response_details": "Yanıt ayrıntıları", "request_details": "İstek ayrıntıları", @@ -108,14 +109,14 @@ "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ı", "blocked_by": "<0>Filtreler tarafından engellenen", "stats_malware_phishing": "Engellenen kötü amaçlı yazılım ve kimlik avı", "stats_adult": "Engellenen yetişkin içerikli siteler", - "stats_query_domain": "En fazla sorgulanan alan adları", + "stats_query_domain": "Başlıca sorgulanan alan adları", "for_last_24_hours": "son 24 saat içindekiler", "for_last_days": "son {{count}} gün boyunca", "for_last_days_plural": "son {{count}} gün boyunca", @@ -123,27 +124,27 @@ "stats_disabled_short": "İstatistikler devre dışı bırakıldı", "no_domains_found": "Alan adı bulunamadı", "requests_count": "İstek sayısı", - "top_blocked_domains": "En fazla engellenen alan adları", - "top_clients": "En aktif istemciler", + "top_blocked_domains": "Başlıca engellenen alan adları", + "top_clients": "Başlıca istemciler", "no_clients_found": "İstemci bulunamadı", "general_statistics": "Genel istatistikler", "number_of_dns_query_days": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı", "number_of_dns_query_days_plural": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı", "number_of_dns_query_24_hours": "Son 24 saat içinde işlenen DNS sorgularının sayısı", - "number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve engel listeleri tarafından engellenen DNS isteklerinin sayısı", + "number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve ana makine engel listeleri tarafından engellenen DNS isteklerinin sayısı", "number_of_dns_query_blocked_24_hours_by_sec": "AdGuard gezinti koruması modülü tarafından engellenen DNS isteklerinin sayısı", "number_of_dns_query_blocked_24_hours_adult": "Engellenen yetişkin içerikli sitelerin sayısı", "enforced_save_search": "Uygulanan güvenli arama", "number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı", "average_processing_time": "Ortalama işlem süresi", "average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi", - "block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar listelerini kullanarak alan adlarını engelle", + "block_domain_use_filters_and_hosts": "Filtre ve ana makine listelerini kullanarak alan adlarını engelle", "filters_block_toggle_hint": "Filtreler ayarlarında engelleme kuralları oluşturabilirsiniz.", "use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan", "use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.", "use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan", "use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.", - "enforce_safe_search": "Güvenli aramayı kullan", + "enforce_safe_search": "Güvenli Aramayı kullan", "enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Yandex ve Pixabay.", "no_servers_specified": "Sunucu belirtilmedi", "general_settings": "Genel ayarlar", @@ -151,7 +152,7 @@ "dns_blocklists": "DNS engel listeleri", "dns_allowlists": "DNS izin listeleri", "dns_blocklists_desc": "AdGuard Home, engel listeleriyle eşleşen alan adlarını engeller.", - "dns_allowlists_desc": "DNS izin listesindeki alan adlarına, engel listesinde olsa bile izin verilir.", + "dns_allowlists_desc": "DNS izin listesindeki alan adlarına, engel listesinde olsa bile izin verilecektir.", "custom_filtering_rules": "Özel filtreleme kuralları", "encryption_settings": "Şifreleme ayarları", "dhcp_settings": "DHCP ayarları", @@ -165,12 +166,12 @@ "enabled_filtering_toast": "Filtreleme etkin", "disabled_safe_browsing_toast": "Güvenli Gezinti devre dışı bırakıldı", "enabled_safe_browsing_toast": "Güvenli Gezinti etkinleştirildi", - "disabled_parental_toast": "Ebeveyn denetimi devre dışı bırakıldı", - "enabled_parental_toast": "Ebeveyn denetimi etkinleştirildi", - "disabled_safe_search_toast": "Güvenli arama devre dışı bırakıldı", - "enabled_save_search_toast": "Güvenli arama etkinleştirildi", + "disabled_parental_toast": "Ebeveyn Denetimi devre dışı bırakıldı", + "enabled_parental_toast": "Ebeveyn Denetimi etkinleştirildi", + "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ı", @@ -179,14 +180,14 @@ "edit_table_action": "Düzenle", "delete_table_action": "Sil", "elapsed": "Geçen süre", - "filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve ana bilgisayar engelleme dosyalarının söz dizimini anlar.", + "filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve ana makine engelleme dosyalarının söz dizimini anlar.", "no_blocklist_added": "Engel listesi eklenmedi", "no_whitelist_added": "İzin listesi eklenmedi", "add_blocklist": "Engel listesi ekle", "add_allowlist": "İzin listesi ekle", "cancel_btn": "İptal", - "enter_name_hint": "İsim girin", - "enter_url_or_path_hint": "Listenin URL adresini veya dosya konumunu 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", "new_allowlist": "Yeni izin listesi", @@ -196,29 +197,32 @@ "choose_allowlist": "İzin listelerini seçin", "enter_valid_blocklist": "Engel listesine geçerli bir URL girin.", "enter_valid_allowlist": "İzin listesine geçerli bir URL girin.", - "form_error_url_format": "URL biçimi geçersiz", - "form_error_url_or_path_format": "Listenin URL adresi veya dosya konumu geçersiz", + "form_error_url_format": "Geçersiz URL biçimi", + "form_error_url_or_path_format": "Geçersiz URL adresi veya dosya yolu", "custom_filter_rules": "Özel filtreleme kuralları", - "custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana bilgisayar dosyası söz dizimi kullanabilirsiniz.", - "system_host_files": "Sistem ana bilgisayar dosyaları", + "custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana makine dosyası söz dizimi kullanabilirsiniz.", + "system_host_files": "Sistem ana makine dosyaları", "examples_title": "Örnekler", - "example_meaning_filter_block": "example.org alan adına ve tüm alt alan adlarına olan erişimi engeller", - "example_meaning_filter_whitelist": "example.org alan adına ve tüm alt alan adlarına olan erişim engelini kaldırır", - "example_meaning_host_block": "AdGuard Home, example.org adresi için 127.0.0.1 adresine yönlendirme yapacaktır (alt alan adları için geçerli değildir)", - "example_comment": "! Buraya bir yorum ekledim", - "example_comment_meaning": "sadece bir yorum", - "example_comment_hash": "# Bir yorum daha ekledim", - "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle", - "example_upstream_regular": "normal DNS (UDP üzerinden)", - "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS", - "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS", - "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ı kullanabilirsiniz", - "example_upstream_tcp": "normal DNS (TCP üzerinden)", + "example_meaning_filter_block": "example.org'a ve tüm alt alanlarına erişimi engeller;", + "example_meaning_filter_whitelist": "example.org'a ve tüm alt alanlarına erişimin engelini kaldırır;", + "example_meaning_host_block": "example.org için 127.0.0.1 ile yanıt verin (ancak alt alanları için değil);", + "example_comment": "! Buraya bir yorum gelir.", + "example_comment_meaning": "sadece bir yorum;", + "example_comment_hash": "# Ayrıca bir yorum.", + "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle.", + "example_upstream_regular": "normal DNS (UDP üzerinden);", + "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;", + "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ı);", "all_lists_up_to_date_toast": "Tüm listeler güncel durumda", "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", @@ -236,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", @@ -259,7 +263,7 @@ "query_log_strict_search": "Tam arama için çift tırnak işareti kullanın", "query_log_retention_confirm": "Sorgu günlüğü saklama süresini değiştirmek istediğinize emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır", "anonymize_client_ip": "İstemcinin IP adresini gizle", - "anonymize_client_ip_desc": "İstemcinin IP adresini günlüklere ve istatistiklere kaydetmeyin", + "anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin", "dns_config": "DNS sunucu yapılandırması", "dns_cache_config": "DNS önbellek yapılandırması", "dns_cache_config_desc": "Burada DNS önbelleğini yapılandırabilirsiniz", @@ -276,8 +280,8 @@ "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", "client_id": "İstemci Kimliği", - "client_id_placeholder": "İstemci kimliği girin", - "client_id_desc": "Farklı istemciler, özel bir istemci kimliği ile tanımlanabilir. Burada istemcileri nasıl tanımlayacağınız hakkında daha fazla bilgi edinebilirsiniz.", + "client_id_placeholder": "İstemci Kimliği girin", + "client_id_desc": "İstemciler, İstemci Kimliği ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi buradan öğrenin.", "download_mobileconfig_doh": "DNS-over-HTTPS için .mobileconfig dosyasını indir", "download_mobileconfig_dot": "DNS-over-TLS için .mobileconfig dosyasını indir", "download_mobileconfig": "Yapılandırma dosyasını indir", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Sıklık limitini girin", "rate_limit": "Sıklık limiti", "edns_enable": "EDNS istemci alt ağını etkinleştir", - "edns_cs_desc": "İstemcilerin alt ağlarını DNS sunucularına gönderin.", + "edns_cs_desc": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı seçeneğini (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.", "rate_limit_desc": "İstemci başına izin verilen saniyedeki istek sayısı. 0 olarak ayarlamak, sınır olmadığı anlamına gelir.", "blocking_ipv4_desc": "Engellenen bir A isteği için geri döndürülecek IP adresi", "blocking_ipv6_desc": "Engellenen bir AAAA isteği için geri döndürülecek IP adresi", @@ -308,7 +312,7 @@ "install_settings_title": "Yönetici Web Arayüzü", "install_settings_listen": "Dinleme arayüzü", "install_settings_port": "Bağlantı noktası", - "install_settings_interface_link": "AdGuard Home yönetici web arayüzü sayfanız şu adresten erişilebilir olacaktır:", + "install_settings_interface_link": "AdGuard Home yönetici web arayüzünüz aşağıdaki adreslerde bulunacaktır:", "form_error_port": "Geçerli bir bağlantı noktası değeri girin", "install_settings_dns": "DNS sunucusu", "install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:", @@ -334,22 +338,22 @@ "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": "Ekranın sol tarafında bulunan \"Adaptör ayarlarını değiştir\" öğesini bulun ve 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 simgesinde bulunan Sistem Tercihleri'ne tıklayın.", - "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.", "install_devices_ios_list_1": "Ana ekrandan Ayarlar'a dokunun.", "install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (mobil ağlar için özel DNS sunucusu ayarlanamaz).", - "install_devices_ios_list_3": "Bağlı olduğunuz ağın ismine dokunun.", + "install_devices_ios_list_3": "O anda aktif olan ağın adına dokunun.", "install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.", "get_started": "Başlayın", "next": "Sonraki", @@ -360,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ğlanır.", + "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ı", - "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_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.", @@ -379,21 +383,21 @@ "encryption_enable": "Şifrelemeyi etkinleştir (HTTPS, DNS-over-HTTPS ve DNS-over-TLS)", "encryption_enable_desc": "Şifrelemeyi etkinleştirirseniz, AdGuard Home yönetici arayüzü HTTPS üzerinden çalışır ve DNS sunucusu, DNS-over-HTTPS ve DNS-over-TLS üzerinden gelen istekleri dinler.", "encryption_chain_valid": "Sertifika zinciri geçerli", - "encryption_chain_invalid": "Sertifika zinciri geçersiz", - "encryption_key_valid": "Bu geçerli bir {{type}} özel anahtar", - "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar", + "encryption_chain_invalid": "Sertifika zinciri geçersiz.", + "encryption_key_valid": "Bu geçerli bir {{type}} özel anahtar.", + "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar.", "encryption_subject": "Konu", "encryption_issuer": "Sağlayan", - "encryption_hostnames": "Ana bilgisayar adları", + "encryption_hostnames": "Ana makine adları", "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?", - "topline_expiring_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını güncelleyin.", + "topline_expiring_certificate": "SSL sertifikanızın süresi sona üzere. <0>Şifreleme ayarlarını güncelleyin.", "topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını güncelleyin.", "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin", - "form_error_port_unsafe": "Bu bağlantı noktası güvenli değil", - "form_error_equal": "Aynı olmamalı", - "form_error_password": "Şifreler uyuşmuyor", + "form_error_port_unsafe": "Güvenli olmayan bağlantı noktası", + "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", @@ -405,26 +409,26 @@ "update_failed": "Otomatik güncelleme başarısız oldu. Elle güncellemek için lütfen bu adımları uygulayın.", "manual_update": "Elle güncellemek için lütfen bu adımları uygulayın.", "processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor", - "clients_title": "İstemciler", - "clients_desc": "AdGuard Home'a bağlı cihazları yapılandırın", + "clients_title": "Kalıcı istemciler", + "clients_desc": "AdGuard Home'a bağlı cihazlar için kalıcı istemci kayıtlarını yapılandırın", "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", "client_edit": "İstemciyi Düzenle", "client_identifier": "Tanımlayıcı", "ip_address": "IP adresi", - "client_identifier_desc": "İstemciler IP adresi, CIDR, MAC adresi veya özel bir istemci kimliği ile tanımlanabilir (DoT/DoH/DoQ için kullanılabilir). İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>burada bulabilirsiniz.", + "client_identifier_desc": "İstemciler IP adresleri, CIDR, MAC adresleri veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>buradan edinebilirsiniz.", "form_enter_ip": "IP girin", "form_enter_subnet_ip": "\"{{cidr}}\" alt ağına bir IP adresi girin", "form_enter_mac": "MAC adresi girin", "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", @@ -432,18 +436,18 @@ "clients_not_found": "İstemci bulunamadı", "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?", "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?", - "auto_clients_title": "İstemciler (çalışma zamanı)", - "auto_clients_desc": "AdGuard Home'u kullanan ancak yapılandırmada depolanmayan istemcilerle ilgili veriler", + "auto_clients_title": "Çalışma zamanı istemcileri", + "auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar", "access_title": "Erişim ayarları", - "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz.", + "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz", "access_allowed_title": "İzin verilen istemciler", - "access_allowed_desc": "CIDR'lerin, IP adreslerinin veya istemci kimliklerinin listesi. Yapılandırılırsa, AdGuard Home yalnızca bu istemcilerden gelen istekleri kabul eder.", + "access_allowed_desc": "CIDR'lerin, IP adreslerinin veya İstemci Kimliklerin listesi. Bu listede girişler varsa, AdGuard Home yalnızca bu istemcilerden gelen istekleri kabul eder.", "access_disallowed_title": "İzin verilmeyen istemciler", - "access_disallowed_desc": "CIDR'lerin, IP adreslerinin veya istemci kimliklerinin listesi. Yapılandırılırsa, AdGuard Home bu istemcilerden gelen istekleri keser. İzin verilen istemciler yapılandırılırsa, bu alan yok sayılır.", + "access_disallowed_desc": "CIDR'lerin, IP adreslerinin veya İstemci Kimliklerin listesi. Bu listede girişler varsa, AdGuard Home bu istemcilerden gelen istekleri keser. İzin verilen istemcilerde girişler varsa, bu alan yok sayılır.", "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", @@ -451,9 +455,9 @@ "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 desteğini ekler.", + "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.", "setup_dns_privacy_ios_2": "<0>iOS için AdGuard, <1>DNS-over-HTTPS ve <1>DNS-over-TLS protokolünü destekler.", "setup_dns_privacy_other_title": "Diğer kullanım alanları", @@ -471,12 +475,12 @@ "rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?", "rewrite_desc": "Belirli bir alan adı için özel DNS yanıtını kolayca yapılandırmanızı sağlar.", "rewrite_applied": "Yeniden yazım kuralı uygulandı", - "rewrite_hosts_applied": "Ana bilgisayar dosyası kuralı tarafından yeniden yazıldı", + "rewrite_hosts_applied": "Ana makine dosyası kuralı tarafından yeniden yazıldı", "dns_rewrites": "DNS yeniden yazımları", "form_domain": "Alan adı veya joker karakter girin", "form_answer": "IP adresi veya alan adı girin", - "form_error_domain_format": "Alan adı biçimi geçersiz", - "form_error_answer_format": "Yanıt biçimi geçersiz", + "form_error_domain_format": "Geçersiz alan adı biçimi", + "form_error_answer_format": "Geçersiz yanıt biçimi", "configure": "Yapılandır", "main_settings": "Ana ayarlar", "block_services": "Belirli hizmetleri engelle", @@ -487,9 +491,9 @@ "blocked_service": "Engellenen hizmet", "block_all": "Tümünü engelle", "unblock_all": "Tüm engellemeyi kaldır", - "encryption_certificate_path": "Sertifika yolu", + "encryption_certificate_path": "Sertifika dosya yolu", "encryption_private_key_path": "Özel anahtar yolu", - "encryption_certificates_source_path": "Sertifika dosyalarının yolunu belirleyin", + "encryption_certificates_source_path": "Bir sertifika dosyası yolu ayarlayın", "encryption_certificates_source_content": "Sertifika içeriğini yapıştır", "encryption_key_source_path": "Özel bir anahtar dosyası belirleyin", "encryption_key_source_content": "Özel anahtar içeriğini yapıştır", @@ -500,6 +504,7 @@ "interval_days": "{{count}} gün", "interval_days_plural": "{{count}} gün", "domain": "Alan adı", + "ecs": "ECS", "punycode": "Punycode", "answer": "Yanıt", "filter_added_successfully": "Liste başarıyla eklendi", @@ -524,7 +529,7 @@ "password_label": "Parola", "password_placeholder": "Parolayı girin", "sign_in": "Giriş yap", - "sign_out": "Oturumu kapat", + "sign_out": "Çıkış yap", "forgot_password": "Parolanızı mı unuttunuz?", "forgot_password_desc": "Kullanıcı hesabınız için yeni bir parola oluşturmak istiyorsanız lütfen <0>bu adımları uygulayın.", "location": "Konum", @@ -532,8 +537,8 @@ "netname": "Ağ adı", "network": "Ağ", "descr": "Açıklama", - "whois": "Whois", - "filtering_rules_learn_more": "Kendi ana bilgisayar listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin.", + "whois": "WHOIS", + "filtering_rules_learn_more": "Kendi ana makine listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin.", "blocked_by_response": "Yanıt olarak CNAME veya IP tarafından engellendi", "blocked_by_cname_or_ip": "CNAME veya IP tarafından engellendi", "try_again": "Tekrar dene", @@ -552,16 +557,16 @@ "autofix_warning_list": "Bu görevleri gerçekleştirecek: <0>Sistem DNSStubListener'ı devre dışı bırakın <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)", "autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.", "tags_title": "Etiketler", - "tags_desc": "İstemciyi tanımlayan etiketleri seçebilirsiniz. Etiketler, filtreleme kurallarına dahil edilebilir ve bunları daha doğru bir şekilde uygulamanıza olanak tanır. <0>Daha fazla bilgi edinin", + "tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dahil edin. <0>Daha fazla bilgi edinin.", "form_select_tags": "İstemci etiketlerini seçin", "check_title": "Filtrelemeyi denetleyin", - "check_desc": "Ana bilgisayar adının filtreleme durumunu kontrol edin", + "check_desc": "Ana makine adının filtreleme durumunu kontrol edin.", "check": "Denetle", - "form_enter_host": "Ana bilgisayar adı girin", + "form_enter_host": "Ana makine adı girin", "filtered_custom_rules": "Özel filtreleme kurallarına göre filtrelendi", "choose_from_list": "Listeden seç", "add_custom_list": "Özel liste ekle", - "host_whitelisted": "Ana bilgisayara izin verildi", + "host_whitelisted": "Ana makineye izin verildi", "check_ip": "IP adresleri: {{ip}}", "check_cname": "CNAME: {{cname}}", "check_reason": "Sebep: {{reason}}", @@ -594,19 +599,19 @@ "allowed": "İzin verilen", "filtered": "Filtrelenen", "rewritten": "Yeniden yazılan", - "safe_search": "Güvenli arama", + "safe_search": "Güvenli Arama", "blocklist": "Engel listesi", "milliseconds_abbreviation": "ms", "cache_size": "Önbellek boyutu", - "cache_size_desc": "DNS önbellek boyutu (bayt cinsinden)", - "cache_ttl_min_override": "Minimum TTL'i değiştir", - "cache_ttl_max_override": "Maksimum TTL'i değiştir", + "cache_size_desc": "DNS önbellek boyutu (bayt cinsinden).", + "cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl", + "cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl", "enter_cache_size": "Önbellek boyutunu girin (bayt)", - "enter_cache_ttl_min_override": "Minimum TTL değerini girin (saniye olarak)", - "enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye olarak)", - "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak)", - "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)", - "ttl_cache_validation": "Minimum önbellek TTL değeri, maksimum değerden küçük veya bu değere eşit olmalıdır", + "enter_cache_ttl_min_override": "Minimum kullanım süresi girin (saniye olarak)", + "enter_cache_ttl_max_override": "Maksimum kullanım süresi girin (saniye olarak)", + "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak).", + "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak).", + "ttl_cache_validation": "Minimum önbellek kullanım süresi geçersiz kılma, maksimum değerden küçük veya ona eşit olmalıdır", "cache_optimistic": "İyimser önbelleğe alma", "cache_optimistic_desc": "Girişlerin süresi dolduğunda bile AdGuard Home'un önbellekten yanıt vermesini sağlayın ve bunları yenilemeye çalışın.", "filter_category_general": "Genel", @@ -621,12 +626,12 @@ "original_response": "Gerçek yanıt", "click_to_view_queries": "Sorguları görmek için tıklayın", "port_53_faq_link": "53 numaralı bağlantı noktası genellikle \"DNSStubListener\" veya \"systemd-resolved\" hizmetleri tarafından kullanılır. Lütfen bu sorunun nasıl çözüleceğine ilişkin <0>bu talimatı okuyun.", - "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayar.", + "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayacaktır.", "filter_allowlist": "UYARI: Bu işlem ayrıca \"{{disallowed_rule}}\" kuralını izin verilen istemciler listesinden hariç tutacaktır.", "last_rule_in_allowlist": "\"{{disallowed_rule}}\" kuralı hariç tutulduğunda \"İzin verilen istemciler\" listesi DEVRE DIŞI bırakılacağı için bu istemciye izin verilemez.", - "experimental": "Deneysel", "use_saved_key": "Önceden kaydedilmiş anahtarı kullan", "parental_control": "Ebeveyn Denetimi", "safe_browsing": "Güvenli Gezinti", - "served_from_cache": "{{value}} (önbellekten kullanıldı)" + "served_from_cache": "{{value}} (önbellekten kullanıldı)", + "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır" } diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json index 42db7636..d89c1da4 100644 --- a/client/src/__locales/uk.json +++ b/client/src/__locales/uk.json @@ -1,22 +1,22 @@ { "client_settings": "Налаштування клієнта", - "example_upstream_reserved": "Ви можете вказати DNS-сервер <0>для певних доменів", - "example_upstream_comment": "Ви можете вказати коментар", + "example_upstream_reserved": "DNS-сервер <0>для певних доменів;", + "example_upstream_comment": "коментар.", "upstream_parallel": "Використовувати паралельні запити, щоб пришвидшити вирішення одночасною чергою всіх оригінальних серверів.", "parallel_requests": "Паралельні запити", "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-сервер увімкнено", @@ -37,16 +37,17 @@ "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_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": "ID клієнта має містити лише цифри, малі букви та дефіси", "form_error_server_name": "Неправильна назва сервера", "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»", - "form_error_positive": "Повинно бути більше 0", + "form_error_positive": "Повинно бути більше за 0", + "form_error_gateway_ip": "Оренда не може мати IP-адресу шлюзу", "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»", "lower_range_start_error": "Має бути меншим за початкову адресу", "greater_range_start_error": "Має бути більшим за початкову адресу", @@ -60,14 +61,14 @@ "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", "dhcp_table_hostname": "Назва вузла", - "dhcp_table_expires": "Термін дії", + "dhcp_table_expires": "Закінчується", "dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!", - "dhcp_error": "Не вдалося визначити, чи є в мережі інший DHCP-сервер.", + "dhcp_error": "AdGuard Home не зміг визначити, чи є в мережі інший DHCP-сервер", "dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.", "dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».", "dhcp_lease_added": "Статичну оренду «{{key}}» успішно додано", @@ -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,21 +132,21 @@ "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-запитів до пошукових систем, для яких примусово застосований безпечний пошук", "average_processing_time": "Середній час обробки", "average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах", - "block_domain_use_filters_and_hosts": "Блокувати домени з використанням фільтрів та hosts-файлів", + "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, що й веб-служба безпечного перегляду.", - "enforce_safe_search": "Використовувати безпечний пошук", + "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": "Не вказано сервери", + "no_servers_specified": "Сервери не вказано", "general_settings": "Загальні налаштування", "dns_settings": "Налаштування DNS", "dns_blocklists": "Список блокування DNS", @@ -158,15 +159,15 @@ "upstream_dns": "Upstream DNS-сервери", "upstream_dns_help": "Введіть адреси серверів по одній на рядок. Докладніше про налаштування DNS-серверів.", "upstream_dns_configured_in_file": "Налаштовано в {{path}}", - "test_upstream_btn": "Тест upstream серверів", + "test_upstream_btn": "Перевірити сервери", "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_parental_toast": "«Батьківський контроль» вимкнено", + "enabled_parental_toast": "«Батьківський контроль» увімкнено", "disabled_safe_search_toast": "Безпечний пошук вимкнено", "enabled_save_search_toast": "Безпечний пошук увімкнено", "enabled_table_header": "Увімкнено", @@ -193,32 +194,35 @@ "edit_blocklist": "Змінити список блокування", "edit_allowlist": "Змінити список дозволів", "choose_blocklist": "Виберіть списки блокування", - "choose_allowlist": "Обрати списки дозволених сайтів", + "choose_allowlist": "Виберіть списки дозволів", "enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.", "enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.", "form_error_url_format": "Неправильний формат URL", - "form_error_url_or_path_format": "Помилкова URL-адреса чи абсолютний шлях до списку", + "form_error_url_or_path_format": "Неправильна URL-адреса або абсолютний шлях до списку", "custom_filter_rules": "Власні правила фільтрування", "custom_filter_rules_hint": "Вводьте одне правило на рядок. Ви можете використовувати правила блокування чи синтаксис файлів hosts.", "system_host_files": "Системні hosts-файли", "examples_title": "Зразки", - "example_meaning_filter_block": "блокує доступ до домену example.org та всіх його піддоменів", - "example_meaning_filter_whitelist": "розблоковує доступ до домену example.org та всіх його піддоменів", - "example_meaning_host_block": "AdGuard Home повертатиме адресу 127.0.0.1 для домену example.org (але не його піддоменів).", - "example_comment": "! Так можна додавати коментар", - "example_comment_meaning": "просто коментар", - "example_comment_hash": "# Це також коментар", - "example_regex_meaning": "блокує доступ до доменів, що відповідають вказаному звичайному виразу", - "example_upstream_regular": "звичайний 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_tcp": "звичайний DNS (через TCP)", + "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": "звичайний DNS (через UDP);", + "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_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": "Заборонити цього клієнта", @@ -243,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}}", @@ -259,12 +263,12 @@ "query_log_strict_search": "Використовуйте подвійні лапки для точного пошуку", "query_log_retention_confirm": "Ви дійсно хочете змінити час зберігання журналу? Якщо ви зменшите значення, деякі дані будуть втрачені", "anonymize_client_ip": "Анонімізація IP-адреси клієнта", - "anonymize_client_ip_desc": "Не зберігайте повну IP-адресу клієнта в журналах і статистиці", + "anonymize_client_ip_desc": "Не зберігати повну IP-адресу клієнта в журналах і статистиці", "dns_config": "Конфігурація DNS-сервера", "dns_cache_config": "Конфігурація кешу DNS", - "dns_cache_config_desc": "Тут ви можете налаштувати кеш DNS", + "dns_cache_config_desc": "Тут ви можете налаштувати DNS-кеш", "blocking_mode": "Режим блокування", - "default": "Типовий", + "default": "Усталено", "nxdomain": "NXDOMAIN", "refused": "REFUSED", "null_ip": "Нульовий IP", @@ -275,9 +279,9 @@ "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": "Різні клієнти можуть бути розпізнані завдяки спеціальному ідентифікатору. Докладніше про ідентифікацію клієнтів.", + "client_id": "ClientID", + "client_id_placeholder": "Введіть ClientID", + "client_id_desc": "Різні клієнти можуть бути розпізнані завдяки ClientID. Докладніше про ідентифікацію клієнтів.", "download_mobileconfig_doh": "Завантажити .mobileconfig для DNS-over-HTTPS", "download_mobileconfig_dot": "Завантажити .mobileconfig для DNS-over-TLS", "download_mobileconfig": "Завантажити файл конфігурації", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "Уведіть обмеження швидкості", "rate_limit": "Обмеження швидкості", "edns_enable": "Увімкнути відправку EDNS Client Subnet", - "edns_cs_desc": "Надсилати підмережі клієнтів на DNS-сервери.", + "edns_cs_desc": "Додавати параметр EDNS Client Subnet (ECS) до запитів до upstream-серверів, а також записувати в журнал значення, що надсилаються клієнтами.", "rate_limit_desc": "Кількість запитів в секунду, які може робити один клієнт. Встановлене значення «0» означатиме необмежену кількість.", "blocking_ipv4_desc": "IP-адреса, яку потрібно видати для заблокованого A запиту", "blocking_ipv6_desc": "IP-адреса, яку потрібно видати для заблокованого АААА запиту", @@ -314,7 +318,7 @@ "install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:", "install_settings_all_interfaces": "Усі інтерфейси", "install_auth_title": "Авторизація", - "install_auth_desc": "Необходно налаштувати автентифікацію паролем для вебінтерфейсу AdGuard Home. Навіть якщо він доступний лише у вашій локальній мережі, важливо захистити його від необмеженого доступу.\n\nДолжна быть настроена аутентификация паролем для веб-интерфейса AdGuard Home. Даже если он доступен только в вашей локальной сети, важно защитить его от неограниченного доступа.", + "install_auth_desc": "Необхідно налаштувати автентифікацію паролем для вебінтерфейсу AdGuard Home. Навіть якщо він доступний лише у вашій локальній мережі, важливо захистити його від необмеженого доступу.", "install_auth_username": "Ім'я користувача", "install_auth_password": "Пароль", "install_auth_confirm": "Підтвердьте пароль", @@ -334,12 +338,12 @@ "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_3": "Зліва на екрані натисніть на «Змінити налаштування адаптера».", + "install_devices_windows_list_4": "Клацніть на активному з'єднанні правою кнопкою миші та виберіть «Властивості».", "install_devices_windows_list_5": "Знайдіть у списку пункт «Internet Protocol Version 4 (TCP/IPv4)» або «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_2": "Виберіть «Мережа».", "install_devices_macos_list_3": "Виберіть перше з'єднання зі списку та натисніть кнопку Додатково.", "install_devices_macos_list_4": "Виберіть вкладку DNS і введіть адреси сервера AdGuard Home.", "install_devices_android_list_1": "На головному екрані меню Android торкніться Налаштування.", @@ -349,18 +353,18 @@ "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": "Наступні", "open_dashboard": "Відкрити інформаційну панель", "install_saved": "Збережено успішно", "encryption_title": "Шифрування", - "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS так і для веб-інтерфейсу адміністратора", + "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS, так і для вебінтерфейсу адміністратора", "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", @@ -368,20 +372,20 @@ "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_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": "Закічнується", + "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_chain_valid": "Ланцюжок довіри сертифікатів дійсний", + "encryption_chain_invalid": "Ланцюжок довіри сертифікатів не дійсний", + "encryption_key_valid": "Дійсний {{type}} приватний ключ", + "encryption_key_invalid": "Недійсний {{type}} приватний ключ", "encryption_subject": "Обє'кт", "encryption_issuer": "Видавець", "encryption_hostnames": "Назви вузлів", @@ -389,22 +393,23 @@ "topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування.", "topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування.", "form_error_port_range": "Введіть значення порту в діапазоні 80−65535", - "form_error_port_unsafe": "Це небезпечний порт", + "form_error_port_unsafe": "Небезпечний порт", "form_error_equal": "Мають бути різні значення", - "form_error_password": "Пароль не співпадає", + "form_error_password": "Паролі не збігаються", "reset_settings": "Скинути налаштування", "update_announcement": "AdGuard Home {{version}} тепер доступний! <0>Докладніше.", "setup_guide": "Посібник з налаштування", "dns_addresses": "DNS-адреси", "dns_start": "DNS-сервер запускається", - "dns_status_error": "Помилка перевірки стану сервера DNS", + "dns_status_error": "Помилка перевірки стану DNS-сервера", "down": "Недоступний", "fix": "Виправити", "dns_providers": "<0>Список відомих DNS-провайдерів на вибір.", "update_now": "Оновити зараз", "update_failed": "Помилка автоматичного оновлення. Будь ласка, виконайте ці кроки аби оновити вручну.", + "manual_update": "Щоб оновити самостійно, виконайте ці кроки.", "processing_update": "Зачекайте будь ласка, AdGuard Home оновлюється", - "clients_title": "Клієнти", + "clients_title": "Постійні клієнти", "clients_desc": "Налаштуйте пристрої, під'єднані до AdGuard Home", "settings_global": "Загальні", "settings_custom": "Власні", @@ -416,7 +421,7 @@ "client_edit": "Редагувати Клієнта", "client_identifier": "Ідентифікатор", "ip_address": "IP-адреса", - "client_identifier_desc": "Клієнтів можна ідентифікувати за IP-, CIDR-, MAC-адресами або ж за спеціальним клієнтським ідентифікатором (можливий для DoT, DoH та DoQ). <0>Докладніше про ідентифікацію клієнтів.", + "client_identifier_desc": "Клієнтів можна ідентифікувати за IP- чи MAC-адресами, CIDR або ж за спеціальним клієнтським ідентифікатором ClientID (можливий для DoT, DoH та DoQ). <0>Докладніше про ідентифікацію клієнтів.", "form_enter_ip": "Введіть IP", "form_enter_subnet_ip": "Введіть IP-адресу в підмережі «{{cidr}}»", "form_enter_mac": "Введіть MAC", @@ -431,18 +436,18 @@ "clients_not_found": "Клієнтів не знайдено", "client_confirm_delete": "Ви впевнені, що хочете видалити клієнта «{{key}}»?", "list_confirm_delete": "Ви впевнені, що хочете видалити цей список?", - "auto_clients_title": "Клієнти (поза налаштуванням)", - "auto_clients_desc": "Дані про клієнтів, які використовують AdGuard Home, але не зберігаються в конфігурації", + "auto_clients_title": "Runtime-клієнти", + "auto_clients_desc": "Клієнти, які використовують AdGuard Home, незалежно від того, чи збережені вони в списку постійних", "access_title": "Налаштування доступу", - "access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home.", + "access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home", "access_allowed_title": "Дозволені клієнти", - "access_allowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home прийматиме запити лише від цих клієнтів.", + "access_allowed_desc": "Перелік CIDR, IP-адрес та ClientIDs. Якщо налаштовано, AdGuard Home прийматиме запити лише від цих клієнтів.", "access_disallowed_title": "Заборонені клієнти", - "access_disallowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список дозволених клієнтів, то це поле проігнорується.", + "access_disallowed_desc": "Перелік CIDR, 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": "Оновлення успішно перевірені", + "updates_checked": "Доступна нова версія AdGuard Home", "updates_version_equal": "AdGuard Home останньої версії", "check_updates_now": "Перевірити наявність оновлень", "dns_privacy": "Конфіденційність DNS", @@ -475,7 +480,7 @@ "form_domain": "Введіть доменне ім’я або підстановний знак", "form_answer": "Введіть IP-адресу або доменне ім'я", "form_error_domain_format": "Неправильний формат домену", - "form_error_answer_format": "Неправильний формат відопвіді", + "form_error_answer_format": "Неправильний формат відповіді", "configure": "Налаштувати", "main_settings": "Головні налаштування", "block_services": "Блокувати конкретні сервіси", @@ -499,6 +504,7 @@ "interval_days": "{{count}} день", "interval_days_plural": "{{count}} дні(в)", "domain": "Домен", + "ecs": "ECS", "punycode": "Punycode", "answer": "Відповідь", "filter_added_successfully": "Фільтр успішно додано", @@ -510,7 +516,7 @@ "statistics_clear": "Очистити статистику", "statistics_clear_confirm": "Ви впевнені, що хочете очистити статистику?", "statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені", - "statistics_cleared": "Статистика успішно очищена", + "statistics_cleared": "Статистику успішно очищено", "statistics_enable": "Увімкнути статистику", "interval_hours": "{{count}} година", "interval_hours_plural": "{{count}} годин(и)", @@ -531,7 +537,7 @@ "netname": "Назва мережі", "network": "Мережа", "descr": "Опис", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Як створити власні списки блокування.", "blocked_by_response": "У відповідь заблоковано по CNAME або IP", "blocked_by_cname_or_ip": "Заблоковано по CNAME або IP", @@ -548,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>Докладніше", + "tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше.", "form_select_tags": "Виберіть теги клієнта", - "check_title": "Перевірте фільтрування", - "check_desc": "Перевірте чи фільтрується назва вузла", + "check_title": "Перевірити фільтрування", + "check_desc": "Перевірити чи фільтрується назва вузла.", "check": "Перевірити", "form_enter_host": "Введіть назву вузла", - "filtered_custom_rules": "Відфільтровано за власними правилами фільтрування", + "filtered_custom_rules": "Відфільтровано завдяки власним правилам фільтрування", "choose_from_list": "Виберіть зі списку", "add_custom_list": "Додати власний список", "host_whitelisted": "Вузол додано до списку дозволів", @@ -581,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": "Відфільтровано", @@ -597,15 +603,15 @@ "blocklist": "Список блокування", "milliseconds_abbreviation": "мс", "cache_size": "Розмір кешу", - "cache_size_desc": "Розмір кешу DNS (у байтах)", - "cache_ttl_min_override": "Замінити мінімальний TTL", - "cache_ttl_max_override": "Замінити максимальний TTL", + "cache_size_desc": "Розмір кешу DNS (у байтах).", + "cache_ttl_min_override": "Змінити мінімальний TTL", + "cache_ttl_max_override": "Змінити максимальний TTL", "enter_cache_size": "Введіть розмір кешу (байт)", - "enter_cache_ttl_min_override": "Введіть мінімальний TTL (секунди)", - "enter_cache_ttl_max_override": "Введіть максимальний TTL (секунди)", - "cache_ttl_min_override_desc": "Розширити короткі значення time-to-live (секунди) отримані від основного сервера під час кешування відповідей DNS", - "cache_ttl_max_override_desc": "Встановіть максимальне значення time-to-live (секунди) для записів у кеші DNS", - "ttl_cache_validation": "Мінімальне значення TTL кеш-пам'яті має бути меншим або рівним максимальному значенню", + "enter_cache_ttl_min_override": "Введіть мінімальний TTL (в секундах)", + "enter_cache_ttl_max_override": "Введіть максимальний TTL (в секундах)", + "cache_ttl_min_override_desc": "Збільшити малі TTL-значення (в секундах), отримані від основного сервера під час кешування DNS-відповідей.", + "cache_ttl_max_override_desc": "Встановіть максимальне TTL-значення (в секундах) для записів у DNS-кеші.", + "ttl_cache_validation": "Мінімальне TTL-значення має бути меншим або рівним максимальному значенню", "cache_optimistic": "Оптимістичне кешування", "cache_optimistic_desc": "AdGuard Home буде відповідати з кешу, навіть якщо відповіді в ньому застарілі, а також спробує оновити їх.", "filter_category_general": "Загальні", @@ -623,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home буде видаляти всі запити DNS із цього клієнта.", "filter_allowlist": "ПОПЕРЕДЖЕННЯ: Таким чином ви також виключите правило «{{disallowed_rule}}» зі списку дозволених клієнтів.", "last_rule_in_allowlist": "Неможливо заблокувати цього клієнта, тому що правило «{{disallowed_rule}}» ВИМКНЕ режим списку дозволів.", - "experimental": "Експериментальний", "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/__locales/vi.json b/client/src/__locales/vi.json index 394801f1..3000a29a 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -1,7 +1,7 @@ { "client_settings": "Cài đặt máy khách", - "example_upstream_reserved": "bạn có thể chỉ định DNS ngược tuyến <0>cho một tên miền cụ thể(hoặc nhiều)", - "example_upstream_comment": "Bạn có thể thêm chú thích cụ thể", + "example_upstream_reserved": "ngược dòng <0>cho các miền cụ thể;", + "example_upstream_comment": "một lời bình luận.", "upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến", "parallel_requests": "Yêu cầu song song", "load_balancing": "Cân bằng tải", @@ -36,14 +36,24 @@ "dhcp_ipv4_settings": "Cài đặt DHCP IPv4", "dhcp_ipv6_settings": "Cài đặt DHCP IPv6", "form_error_required": "Trường bắt buộc", - "form_error_ip4_format": "Định dạng IPv4 không hợp lệ", - "form_error_ip6_format": "Định dạng IPv6 không hợp lệ", + "form_error_ip4_format": "Địa chỉ IPv4 không hợp lệ", + "form_error_ip4_range_start_format": "Địa chỉ IPv4 không hợp lệ của phạm vi bắt đầu", + "form_error_ip4_range_end_format": "Địa chỉ IPv4 không hợp lệ của cuối phạm vi", + "form_error_ip4_gateway_format": "Địa chỉ IPv4 không hợp lệ của cổng kết nối", + "form_error_ip6_format": "Địa chỉ IPv6 không hợp lệ", "form_error_ip_format": "Địa chỉ IP không hợp lệ", - "form_error_mac_format": "Định dạng MAC không hợp lệ", - "form_error_client_id_format": "Định dạng client ID không hợp lệ", + "form_error_mac_format": "Địa chỉ MAC không hợp lệ", + "form_error_client_id_format": "ClientID chỉ được chứa số, chữ thường và dấu gạch nối", "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", + "greater_range_end_error": "Phải lớn hơn phạm vi kết thúc", + "subnet_error": "Địa chỉ phải nằm trong một mạng con", + "gateway_or_subnet_invalid": "Mặt nạ mạng con không hợp lệ", "dhcp_form_gateway_input": "Cổng IP", "dhcp_form_subnet_input": "Mặt nạ mạng con", "dhcp_form_range_title": "Phạm vi của địa chỉ IP", @@ -58,7 +68,7 @@ "dhcp_table_hostname": "Tên máy chủ", "dhcp_table_expires": "Hết hạn", "dhcp_warning": "Nếu bạn vẫn muốn bật máy chủ DHCP, hãy đảm bảo rằng không có máy chủ DHCP hoạt động nào khác trong mạng của bạn. Nếu không, nó có thể phá vỡ Internet cho các thiết bị được kết nối!", - "dhcp_error": "Chúng tôi không thể xác định liệu có một máy chủ DHCP khác trong mạng hay không.", + "dhcp_error": "Chúng tôi không thể xác định liệu có một máy chủ DHCP khác trong mạng hay không", "dhcp_static_ip_error": "Để sử dụng máy chủ DHCP, phải đặt địa chỉ IP tĩnh. Chúng tôi không thể xác định xem giao diện mạng này có được cấu hình bằng địa chỉ IP tĩnh hay không. Vui lòng đặt địa chỉ IP tĩnh theo cách thủ công.", "dhcp_dynamic_ip_found": "Hệ thống của bạn sử dụng cấu hình địa chỉ IP động cho giao diện <0>{{interfaceName}}. Để sử dụng máy chủ DHCP, phải đặt địa chỉ IP tĩnh. Địa chỉ IP hiện tại của bạn là <0>{{ipAddress}}. Chúng tôi sẽ tự động đặt địa chỉ IP này thành tĩnh nếu bạn nhấn nút Bật DHCP.", "dhcp_lease_added": "Cho thuê tĩnh \"{{key}}\" đã được thêm thành công", @@ -191,24 +201,28 @@ "form_error_url_or_path_format": "Định dạng URL hoặc đường dẫn tới danh sách không hợp lệ", "custom_filter_rules": "Quy tắc lọc tuỳ chỉnh", "custom_filter_rules_hint": "Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host", + "system_host_files": "Hệ thống lưu trữ tệp", "examples_title": "Ví dụ", - "example_meaning_filter_block": "Chặn truy cập tới tên miền example.org và tất cả tên miền con", - "example_meaning_filter_whitelist": "Không chặn truy cập tới tên miền example.org và tất cả tên miền con", - "example_meaning_host_block": "AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)", - "example_comment": "! Đây là một chú thích", - "example_comment_meaning": "Chỉ là một chú thích", - "example_comment_hash": "# Cũng là một chú thích", - "example_regex_meaning": "chặn quyền truy cập vào các miền khớp với <0>biểu thức chính được quy định", - "example_upstream_regular": "DNS thông thường (dùng UDP)", - "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", + "example_meaning_filter_block": "chặn truy cập tới tên miền example.org và tất cả tên miền con;", + "example_meaning_filter_whitelist": "không chặn truy cập tới tên miền example.org và tất cả tên miền con;", + "example_meaning_host_block": "hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con);", + "example_comment": "! Đây là một chú thích.", + "example_comment_meaning": "chỉ là một chú thích;", + "example_comment_hash": "# Cũng là một chú thích.", + "example_regex_meaning": "chặn quyền truy cập vào các miền khớp với biểu thức chính được quy định.", + "example_upstream_regular": "DNS thông thường (dùng UDP);", + "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;", "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": "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", @@ -265,9 +279,9 @@ "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", "dns_over_quic": "DNS-over-QUIC", - "client_id": "ID khách hàng", - "client_id_placeholder": "Nhập ID khách hàng", - "client_id_desc": "Các khách hàng khác nhau có thể được xác định bằng một ID khách hàng đặc biệt. Tại đây bạn có thể tìm hiểu thêm về cách xác định khách hàng.", + "client_id": "ClientID", + "client_id_placeholder": "Nhập một ClientID", + "client_id_desc": "Khách hàng có thể được xác định bằng ClientID. Tìm hiểu thêm về cách xác định khách hàng tại đây .", "download_mobileconfig_doh": "Tải xuống .mobileconfig cho DNS-over-HTTPS", "download_mobileconfig_dot": "Tải xuống .mobileconfig cho DNS-over-TLS", "download_mobileconfig": "Tải xuống tệp cấu hình", @@ -275,7 +289,7 @@ "form_enter_rate_limit": "Nhập giới hạn yêu cầu", "rate_limit": "Giới hạn yêu cầu", "edns_enable": "Bật mạng con EDNS Client", - "edns_cs_desc": "Nếu được bật, AdGuard Home sẽ gửi các mạng con của khách hàng đến các máy chủ DNS.", + "edns_cs_desc": "Thêm tùy chọn EDNS Client Subnet (ECS) vào các yêu cầu ngược dòng và ghi lại các giá trị được gửi bởi các máy khách trong nhật ký truy vấn.", "rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)", "blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn", "blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn", @@ -299,12 +313,12 @@ "install_settings_listen": "Giao diện nghe", "install_settings_port": "Cổng", "install_settings_interface_link": "Giao diện web quản trị viên AdGuard Home của bạn sẽ có sẵn trên các địa chỉ sau:", - "form_error_port": "Nhập giá trị cổng hợp lệ", + "form_error_port": "Nhập số cổng hợp lệ", "install_settings_dns": "Máy chủ DNS", "install_settings_dns_desc": "Bạn sẽ cần định cấu hình thiết bị hoặc bộ định tuyến của mình để sử dụng máy chủ DNS trên các địa chỉ sau:", "install_settings_all_interfaces": "Tất cả các giao diện", "install_auth_title": "Xác thực", - "install_auth_desc": "Rất khuyến khích cấu hình xác thực mật khẩu cho giao diện web quản trị viên AdGuard Home của bạn. Ngay cả khi nó chỉ có thể truy cập được trong mạng cục bộ của bạn, điều quan trọng là phải bảo vệ nó khỏi sự truy cập không hạn chế.", + "install_auth_desc": "Xác thực mật khẩu cho giao diện web quản trị AdGuard Home của bạn phải được định cấu hình. Ngay cả khi AdGuard Home chỉ có thể truy cập được trong mạng cục bộ của bạn, điều quan trọng vẫn là bảo vệ nó khỏi quyền truy cập không hạn chế.", "install_auth_username": "Tên đăng nhập", "install_auth_password": "Mật khẩu", "install_auth_confirm": "Xác nhận mật khẩu", @@ -346,11 +360,11 @@ "open_dashboard": "Mở bảng điều khiển", "install_saved": "Lưu thành công", "encryption_title": "Mã hóa", - "encryption_desc": "Hỗ trợ mã hóa (HTTPS/TLS) cho cả giao diện web quản trị viên và DNS", + "encryption_desc": "Hỗ trợ mã hóa (HTTPS/QUIC/TLS) cho cả giao diện web quản trị viên và DNS", "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", @@ -358,7 +372,7 @@ "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", - "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_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.", @@ -370,8 +384,8 @@ "encryption_enable_desc": "Nếu mã hóa được bật, giao diện quản trị viên AdGuard Home sẽ hoạt động trên HTTPS và máy chủ DNS sẽ lắng nghe các yêu cầu qua DNS-over-HTTPS và DNS-over-TLS.", "encryption_chain_valid": "Chứng chỉ hợp lệ", "encryption_chain_invalid": "Chứng chỉ không hợp lệ", - "encryption_key_valid": "Đây là khóa riêng {{type}} hợp lệ", - "encryption_key_invalid": "Đây là khóa riêng {{type}} không hợp lệ", + "encryption_key_valid": "Khóa riêng {{type}} hợp lệ", + "encryption_key_invalid": "Khóa riêng {{type}} không hợp lệ", "encryption_subject": "Chủ đề", "encryption_issuer": "Phát hành", "encryption_hostnames": "Tên máy chủ", @@ -393,9 +407,10 @@ "dns_providers": "Dưới đây là một <0>danh sách của các nhà cung cấp DNS đã biết để lựa chọn.", "update_now": "Cập nhật ngay", "update_failed": "Tự động cập nhật thất bại. Vui lòng làm theo các bước để cập nhật thủ công.", + "manual_update": "Vui lòng làm theo các bước này để cập nhật thủ công.", "processing_update": "Xin vui lòng chờ, AdGuard Home đang được cập nhật", - "clients_title": "Máy khách", - "clients_desc": "Định cấu hình thiết bị được kết nối với AdGuard Home", + "clients_title": "Khách hàng lâu dài", + "clients_desc": "Định cấu hình hồ sơ khách hàng liên tục cho các thiết bị được kết nối với AdGuard Home", "settings_global": "Toàn cầu", "settings_custom": "Tùy chỉnh", "table_client": "Máy khách", @@ -406,7 +421,7 @@ "client_edit": "Chỉnh Sửa Máy Khách", "client_identifier": "Định danh", "ip_address": "Địa chỉ IP", - "client_identifier_desc": "Các máy khách có thể được xác định bằng địa chỉ IP, CIDR, địa chỉ MAC hoặc một ID khách đặc biệt(có thể được dùng cho DoT/DoH/DoQ). Xem thêm cách xác định máy khách tại <0>đây.", + "client_identifier_desc": "Khách hàng có thể được xác định bằng địa chỉ IP, CIDR, địa chỉ MAC hoặc ClientID (có thể được sử dụng cho DoT / DoH / DoQ). Tìm hiểu thêm về cách xác định khách hàng <0>tại đây.", "form_enter_ip": "Nhập IP", "form_enter_subnet_ip": "Nhập địa chỉ IP vào mạng con \"{{cidr}}\"", "form_enter_mac": "Nhập MAC", @@ -422,17 +437,17 @@ "client_confirm_delete": "Bạn có chắc chắn muốn xóa máy khách \"{{key}}\" không?", "list_confirm_delete": "Bạn có muốn xóa bộ lọc này?", "auto_clients_title": "Máy khách (thời gian chạy)", - "auto_clients_desc": "Dữ liệu trên các máy khách sử dụng AdGuard Home, nhưng không được lưu trong cấu hình", + "auto_clients_desc": "Các thiết bị không có trong danh sách khách hàng ổn định vẫn có thể sử dụng AdGuard Home", "access_title": "Cài đặt truy cập", - "access_desc": "Tại đây bạn có thể định cấu hình quy tắc truy cập cho máy chủ AdGuard Home DNS.", + "access_desc": "Tại đây bạn có thể định cấu hình quy tắc truy cập cho máy chủ AdGuard Home DNS", "access_allowed_title": "Máy chủ được phép", - "access_allowed_desc": "Một danh sách các địa chỉ CIDR hoặc IP. Nếu được định cấu hình, AdGuard Home sẽ chỉ chấp nhận các yêu cầu từ các địa chỉ IP này.", + "access_allowed_desc": "Danh sách CIDR, địa chỉ IP hoặc ClientID. Nếu danh sách này có các mục nhập, AdGuard Home sẽ chỉ chấp nhận yêu cầu từ những khách hàng này.", "access_disallowed_title": "Máy chủ không được phép", - "access_disallowed_desc": "Một danh sách các địa chỉ CIDR hoặc IP. Nếu được định cấu hình, AdGuard Home sẽ bỏ yêu cầu từ các địa chỉ IP này.", + "access_disallowed_desc": "Danh sách CIDR, địa chỉ IP hoặc ClientID. Nếu danh sách này có các mục nhập, AdGuard Home sẽ loại bỏ các yêu cầu từ những khách hàng này. Trường này bị bỏ qua nếu có các mục nhập trong máy khách Được phép.", "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ư", @@ -489,6 +504,7 @@ "interval_days": "{{count}} ngày", "interval_days_plural": "{{count}} ngày", "domain": "Tên miền", + "ecs": "ECS", "punycode": "Punycode", "answer": "Trả lời", "filter_added_successfully": "Thêm bộ lọc thành công", @@ -521,7 +537,7 @@ "netname": "Tên mạng", "network": "Mạng", "descr": "Mô tả", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>Tìm hiểu thêm về việc tạo danh sách chặn máy chủ của riêng bạn.", "blocked_by_response": "Chặn bởi CNAME hoặc địa IP ở phản hồi", "blocked_by_cname_or_ip": "Đã bị chặn bởi CNAME hoặc IP", @@ -541,10 +557,10 @@ "autofix_warning_list": "Nó sẽ thực hiện các tác vụ sau: <0> Hủy kích hoạt hệ thống DNSStubListener <0> Đặt địa chỉ máy chủ DNS thành 127.0.0.1 <0> Thay thế mục tiêu liên kết tượng trưng của /etc/resolv.conf bằng / run / systemd /resolve/resolv.conf <0> Dừng DNSStubListener (tải lại dịch vụ do hệ thống phân giải) ", "autofix_warning_result": "Do đó, tất cả các yêu cầu DNS từ hệ thống của bạn sẽ được AdGuard Home xử lý theo mặc định.", "tags_title": "Thẻ", - "tags_desc": "Bạn có thể chọn các thẻ tương ứng với khách hàng. Thẻ có thể được bao gồm trong các quy tắc lọc và cho phép bạn áp dụng chúng chính xác hơn. <0>Tìm hiểu thêm", + "tags_desc": "Bạn có thể chọn các thẻ tương ứng với máy khách. Bao gồm các thẻ trong các quy tắc lọc để áp dụng chúng chính xác hơn. <0>Tìm hiểu thêm.", "form_select_tags": "Chọn thẻ khách hàng", "check_title": "Kiểm tra bộ lọc", - "check_desc": "Kiểm tra xem tên miền có tồn tại trong các bộ lọc không", + "check_desc": "Kiểm tra xem tên miền có tồn tại trong các bộ lọc không.", "check": "Kiểm tra", "form_enter_host": "Nhập tên máy chủ", "filtered_custom_rules": "Được lọc bởi các quy tắc lọc tùy chỉnh", @@ -587,15 +603,17 @@ "blocklist": "Danh sách chặn", "milliseconds_abbreviation": "ms", "cache_size": "Kích thước cache", - "cache_size_desc": "Kích thước cache DNS (bytes)", + "cache_size_desc": "Kích thước cache DNS (bytes).", "cache_ttl_min_override": "Ghi đè TTL tối thiểu", "cache_ttl_max_override": "Ghi đè TTL tối đa", "enter_cache_size": "Nhập kích thước bộ nhớ cache (byte)", "enter_cache_ttl_min_override": "Nhập TTL tối thiểu (giây)", "enter_cache_ttl_max_override": "Nhập TTL tối đa (giây)", - "cache_ttl_min_override_desc": "Mở rộng giá trị thời gian tồn tại ngắn (giây) nhận được từ máy chủ ngược dòng khi phản hồi DNS vào bộ nhớ đệm", - "cache_ttl_max_override_desc": "Đặt giá trị thời gian tồn tại tối đa (giây) cho các mục nhập trong bộ nhớ cache DNS", + "cache_ttl_min_override_desc": "Mở rộng giá trị thời gian tồn tại ngắn (giây) nhận được từ máy chủ ngược dòng khi phản hồi DNS vào bộ nhớ đệm.", + "cache_ttl_max_override_desc": "Đặt giá trị thời gian tồn tại tối đa (giây) cho các mục nhập trong bộ nhớ cache DNS.", "ttl_cache_validation": "Giá trị TTL trong bộ nhớ cache tối thiểu phải nhỏ hơn hoặc bằng giá trị lớn nhất", + "cache_optimistic": "Bộ nhớ đệm lạc quan", + "cache_optimistic_desc": "Làm cho AdGuard Home phản hồi từ bộ nhớ cache ngay cả khi các mục nhập đã hết hạn và cố gắng làm mới chúng.", "filter_category_general": "Chung", "filter_category_security": "Bảo mật", "filter_category_regional": "Khu vực", @@ -611,6 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.", "filter_allowlist": "CẢNH BÁO: Hành động này cũng sẽ loại trừ quy tắc \"{{disallowed_rule}}\" khỏi danh sách các ứng dụng khách được phép.", "last_rule_in_allowlist": "Không thể không cho phép ứng dụng khách này vì việc loại trừ quy tắc \"{{disallowed_rule}}\" sẽ TẮT danh sách \"Ứng dụng khách được phép\".", - "experimental": "Thử nghiệm", - "parental_control": "Quản lý của phụ huynh" + "use_saved_key": "Sử dụng khóa đã lưu trước đó", + "parental_control": "Quản lý của phụ huynh", + "safe_browsing": "Duyệt web an toàn", + "served_from_cache": "{{value}} (được phục vụ từ bộ nhớ cache)", + "form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự" } diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index b24dc0c4..7c777c1d 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -1,7 +1,7 @@ { "client_settings": "客户端设置", - "example_upstream_reserved": "您可以<0>为特定域名指定上游 DNS 服务器", - "example_upstream_comment": "您可以指定注解", + "example_upstream_reserved": "指定为<0>特定域名的上游服务器;", + "example_upstream_comment": "注释。", "upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。", "parallel_requests": "并行请求", "load_balancing": "负载均衡", @@ -43,10 +43,11 @@ "form_error_ip6_format": "无效的 IPv6 地址", "form_error_ip_format": "无效的 IP 地址", "form_error_mac_format": "无效的 MAC 地址", - "form_error_client_id_format": "无效的客户端 ID 格式客户 ID 必须只包含数字、小写字母和连字符", + "form_error_client_id_format": "客户端 ID 必须只包含数字、小写字母和连字符", "form_error_server_name": "无效的服务器名", "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"", "form_error_positive": "必须大于 0", + "form_error_gateway_ip": "租约期限不能有网关的 IP 地址", "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"", "lower_range_start_error": "必须小于范围起始值", "greater_range_start_error": "必须大于范围起始值", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "主机名", "dhcp_table_expires": "到期", "dhcp_warning": "如果你想要启用内置的 DHCP 服务器,请确保在当前网络中没有其它起作用的 DHCP 服务器。否则,此操作可能会破坏已连接设备的网络连接!", - "dhcp_error": "AdGuard Home 无法确定在当前网络中是否存在其它 DHCP 服务器。", + "dhcp_error": "AdGuard Home 无法确定在当前网络中是否存在其它 DHCP 服务器", "dhcp_static_ip_error": "要使用 DHCP 服务器,则必须设置静态 IP 地址。AdGuard Home 无法确定此网络接口是否已被配置为使用静态 IP 地址。请手动为此网络接口设置静态 IP 地址。", "dhcp_dynamic_ip_found": "您的系统对网络接口 <0>{{interfaceName}} 使用了动态 IP 地址配置。要使用 DHCP 服务器,则必须对此网络接口使用静态 IP 地址配置。此网络接口当前的 IP 地址为 <0>{{ipAddress}}。如您点击“启用 DHCP 服务器” 按钮,AdGuard Home 将自动修改此网络接口以使用静态 IP 地址。", "dhcp_lease_added": "静态租约 \"{{key}}\" 已成功添加", @@ -148,8 +149,8 @@ "no_servers_specified": "未找到指定的服务器", "general_settings": "常规设置", "dns_settings": "DNS 设置", - "dns_blocklists": "DNS封锁清单", - "dns_allowlists": "DNS允许清单", + "dns_blocklists": "DNS 拦截列表", + "dns_allowlists": "DNS 允许列表", "dns_blocklists_desc": "AdGuard Home将阻止匹配DNS拦截清单的域名", "dns_allowlists_desc": "来自DNS允许列表的域将被允许,即使它们位于任意阻止列表中也是如此", "custom_filtering_rules": "自定义过滤规则", @@ -196,29 +197,32 @@ "choose_allowlist": "选择允许列表", "enter_valid_blocklist": "输入有效的阻止列表URL", "enter_valid_allowlist": "输入有效的允许列表URL", - "form_error_url_format": "无效的URL格式", - "form_error_url_or_path_format": "无效的URL或列表的绝对路径", + "form_error_url_format": "无效的 URL 格式", + "form_error_url_or_path_format": "无效的 URL 或列表的绝对路径", "custom_filter_rules": "自定义过滤器规则", "custom_filter_rules_hint": "请确保每行只输入一条规则。你可以输入符合 adblock 语法或 Hosts 语法的规则。", "system_host_files": "系统主机文件", "examples_title": "范例", - "example_meaning_filter_block": "拦截 example.org 域名及其所有子域名", - "example_meaning_filter_whitelist": "放行 example.org 及其所有子域名", - "example_meaning_host_block": "AdGuard Home 现在将会把 example.org(但不包括它的子域名)解析到 127.0.0.1。", - "example_comment": "! 这是一行注释", - "example_comment_meaning": "只是一条注释", - "example_comment_hash": "# 这也是一行注释", - "example_regex_meaning": "阻止访问与<0>指定的正则表达式匹配的域", - "example_upstream_regular": "常规 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": "你可以使用 <1>DNSCrypt 的 <0>DNS Stamps 或者 <2>DNS-over-HTTPS 解析器", - "example_upstream_tcp": "常规 DNS(基于 TCP )", + "example_meaning_filter_block": "阻止 example.org 域名及其所有子域名;", + "example_meaning_filter_whitelist": "解除 example.org 及其所有子域名的封锁;", + "example_meaning_host_block": "对 example.org(不包括它的子域名)以 127.0.0.1 作为响应;", + "example_comment": "! 这是一行注释。", + "example_comment_meaning": "只是一条注释;", + "example_comment_hash": "# 这也是一行注释。", + "example_regex_meaning": "阻止访问与指定的正则表达式匹配的域名。", + "example_upstream_regular": "常规 DNS(基于 UDP);", + "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": "<1>DNSCrypt 的 <0>DNS Stamps 或者 <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_test_ok_toast": "指定的 DNS 服务器现已正常运行", "dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确", + "dns_test_warning_toast": "上游 “{{key}}” 不响应测试请求,可能无法正常工作", "unblock": "放行", "block": "拦截", "disallow_this_client": "不允许这个客户端", @@ -259,10 +263,10 @@ "query_log_strict_search": "使用双引号进行严谨搜索", "query_log_retention_confirm": "您确定要更改查询记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。", "anonymize_client_ip": "匿名化客户端IP", - "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整IP地址", + "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址", "dns_config": "DNS 服务配置", "dns_cache_config": "DNS缓存配置", - "dns_cache_config_desc": "你可以在此处配置 DNS缓存", + "dns_cache_config_desc": "您可以在此处配置 DNS 缓存", "blocking_mode": "拦截模式", "default": "默认", "nxdomain": "无效域名", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-over-QUIC", "client_id": "客户端 ID", "client_id_placeholder": "输入客户端 ID", - "client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在 这里你可以了解到更多关于如何识别客户端的信息。", + "client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在这里您可以了解到更多关于如何识别客户端的信息。", "download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig", "download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig", "download_mobileconfig": "下载配置文件", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "输入限制速率", "rate_limit": "速度限制", "edns_enable": "启用 EDNS 客户端子网", - "edns_cs_desc": "将客户端子网发送至 DNS 服务器。", + "edns_cs_desc": "在上游请求中加入 EDNS 客户端子网(“EDNS Client Subnet”,即 ECS)选项,并在查询日志中记录客户端发送的数值。", "rate_limit_desc": "每个客户端每秒钟查询次数的限制。设置为 0 意味着不限制。", "blocking_ipv4_desc": "拦截 A 记录请求返回的 IP 地址", "blocking_ipv6_desc": "拦截 AAAA 记录请求返回的 IP 地址", @@ -333,22 +337,22 @@ "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": "点击进入 ”网络和 Internet“ 后,再次点击进入 “网络和共享中心”", - "install_devices_windows_list_3": "在窗口的左侧找到 ”更改适配器设置“ 并点击进入。", - "install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择 ”属性“ 。", - "install_devices_windows_list_5": "在列表中找到 ”Internet 协议版本 4 (TCP/IPv4)“ ,选择并再次点击 ”属性“ 。", + "install_devices_windows_list_2": "点击进入「网络和 Internet」后,再次点击进入「网络和共享中心」", + "install_devices_windows_list_3": "在窗口的左侧点击「更改适配器设置」。", + "install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择「属性”」。", + "install_devices_windows_list_5": "在列表中找到「Internet 协议版本 4 (TCP/IPv4)」,选择并再次点击「属性」。", "install_devices_windows_list_6": "选择“使用下面的 DNS 服务器地址”,并输入您的 AdGuard Home 服务器地址。", - "install_devices_macos_list_1": "点击苹果图标,进入 ”系统首选项“。", - "install_devices_macos_list_2": "点击 ”网络“ 。", - "install_devices_macos_list_3": "选择在列表中的第一个连接,并点击 ”高级“ 。", - "install_devices_macos_list_4": "选择 ”DNS“ 选项卡,并输入您的 AdGuard Home 服务器地址。", + "install_devices_macos_list_1": "点击苹果图标,进入「系统首选项」。", + "install_devices_macos_list_2": "点击「网络」。", + "install_devices_macos_list_3": "选择在列表中的第一个连接,并点击「高级」。", + "install_devices_macos_list_4": "选择「DNS」选项卡,并输入您的 AdGuard Home 服务器地址。", "install_devices_android_list_1": "在安卓主屏幕菜单中点击设置。", - "install_devices_android_list_2": "点击菜单上的 ”无线局域网“ 选项。在屏幕上将列出所有可用的网络(蜂窝移动网络不支持修改 DNS )。", - "install_devices_android_list_3": "长按当前已连接的网络,然后点击 ”修改网络设置“ 。", - "install_devices_android_list_4": "在某些设备上,您可能需要选中 ”高级“ 复选框以查看进一步的设置。您可能需要调整您安卓设备的 DNS 设置,或是需要将 IP 设置从 DHCP 切换到静态。", + "install_devices_android_list_2": "点击菜单上的「无线局域网」选项。在屏幕上将列出所有可用的网络(蜂窝移动网络不支持修改 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": "从左侧目录中选择 ”无线局域网“(移动数据网络环境下不支持修改 DNS )。", + "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": "开始配置", @@ -356,11 +360,11 @@ "open_dashboard": "打开仪表盘", "install_saved": "保存成功", "encryption_title": "加密", - "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/TLS)", + "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/QUIC/TLS)", "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 端口", @@ -368,7 +372,7 @@ "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_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-QUIC 服务器。", "encryption_certificates": "证书", "encryption_certificates_desc": "为了使用加密,您需要为域提供有效的 SSL 证书链。您可以在 <0>{{link}} 上获得免费证书,也可以从受信任的证书颁发机构购买证书。", "encryption_certificates_input": "将您以 PEM 格式编码的证书复制粘贴到此处。", @@ -378,10 +382,10 @@ "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_chain_valid": "证书链有效", + "encryption_chain_invalid": "证书链无效", + "encryption_key_valid": "该 {{type}} 私钥有效", + "encryption_key_invalid": "该 {{type}} 私钥无效", "encryption_subject": "使用者", "encryption_issuer": "颁发者", "encryption_hostnames": "主机名", @@ -397,7 +401,7 @@ "setup_guide": "设置指导", "dns_addresses": "DNS 地址", "dns_start": "正在启动DNS服务", - "dns_status_error": "检查DNS服务器状态时出错", + "dns_status_error": "检查 DNS 服务器状态时出错", "down": "下移", "fix": "修复", "dns_providers": "此为可从中选择的<0>已知 DNS 提供商列表。", @@ -405,8 +409,8 @@ "update_failed": "自动更新失败。请跟随这些步骤以手动更新。", "manual_update": "请跟随此步骤以进行手动更新。", "processing_update": "正在更新 AdGuard Home,请稍侯", - "clients_title": "客户端", - "clients_desc": "配置已连接到 AdGuard Home 的设备", + "clients_title": "持久客户端", + "clients_desc": "配置已连接到 AdGuard Home 的设备的持久客户端记录", "settings_global": "全局", "settings_custom": "自定义", "table_client": "客户端", @@ -417,7 +421,7 @@ "client_edit": "编辑客户端", "client_identifier": "标识符", "ip_address": "IP 地址", - "client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或特殊 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里您可多了解如何识别客户端。", + "client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或客户端 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里您可多了解如何识别客户端。", "form_enter_ip": "输入 IP", "form_enter_subnet_ip": "输入一个 IP 地址,其须位于子网\"{{cidr}}\"", "form_enter_mac": "输入 MAC", @@ -433,17 +437,17 @@ "client_confirm_delete": "您确定要删除客户端 \"{{key}}\"?", "list_confirm_delete": "您确定要删除此列表吗?", "auto_clients_title": "客户端(运行时间)", - "auto_clients_desc": "使用 Adguard Home 但未存储在配置中的客户端上的数据", + "auto_clients_desc": "不在可继续使用 AdGuard Home 的持久客户端列表中的设备。", "access_title": "访问设置", - "access_desc": "您可在此处配置 AdGuard Home DNS 服务器的访问规则。", + "access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则", "access_allowed_title": "允许的客户端", - "access_allowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如已配置,则 AdGuard Home 将仅接受来自这些客户端的请求。", + "access_allowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如已配置,则 AdGuard Home 将仅接受来自这些客户端的请求。", "access_disallowed_title": "不允许的客户端", - "access_disallowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如果已配置,则 AdGuard Home 将丢弃来自这些 IP 地址的请求。如果允许的客户端已配置,此字段将会被忽略。", + "access_disallowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如果已配置,则 AdGuard Home 将丢弃来自这些客户端的请求。如果允许的客户端已配置,此字段将会被忽略。", "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 隐私", @@ -500,6 +504,7 @@ "interval_days": "{{count}} 天", "interval_days_plural": "{{count}} 天", "domain": "域名", + "ecs": "ECS", "punycode": "Punycode", "answer": "应答", "filter_added_successfully": "已成功添加过滤器", @@ -532,7 +537,7 @@ "netname": "网络名称", "network": "网络", "descr": "描述", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>了解更多关于创建自己的hosts清单。", "blocked_by_response": "因响应的CNAME或IP被屏蔽", "blocked_by_cname_or_ip": "按CNAME或IP拦截", @@ -552,10 +557,10 @@ "autofix_warning_list": "其将会进行如下工作:<0>停用系统DNSStubListener<0>设置DNS服务器地址为127.0.0.1<0>将/etc/resolv.conf的符号链接目标替换为/run/systemd/resolv/resolv.conf<0>停止DNSStubListener(重新加载系统解析服务)", "autofix_warning_result": "因此,默认情况下所有来自系统的DNS请求都将由AdGuardHome处理。", "tags_title": "标签", - "tags_desc": "您可以选择与客户端对应的标记。标签可以包含在过滤规则中,并允许您更准确地应用它们。<0>了解更多", + "tags_desc": "您可以选择与客户端对应的标记。标签可以包含在过滤规则中,并允许您更准确地应用它们。<0>了解更多。", "form_select_tags": "选择客户端标签", "check_title": "检查过滤", - "check_desc": "检查主机名是否被过滤", + "check_desc": "检查主机名是否被过滤。", "check": "检查", "form_enter_host": "输入主机名称", "filtered_custom_rules": "被自定义过滤规则过滤", @@ -598,15 +603,15 @@ "blocklist": "拦截列表", "milliseconds_abbreviation": "毫秒", "cache_size": "缓存大小", - "cache_size_desc": "DNS缓存大小 (单位:字节)", + "cache_size_desc": "DNS 缓存大小(单位:字节)。", "cache_ttl_min_override": "覆盖最小TTL值", "cache_ttl_max_override": "覆盖最大TTL值", "enter_cache_size": "输入缓存大小(字节)", "enter_cache_ttl_min_override": "输入最小 TTL 值(秒)", "enter_cache_ttl_max_override": "输入最大 TTL 值(秒)", - "cache_ttl_min_override_desc": "缓存 DNS 响应时,延长从上游服务器接收到的 TTL 值 (秒)", - "cache_ttl_max_override_desc": "设定 DNS 缓存条目的最大 TTL 值(秒)", - "ttl_cache_validation": "最小缓存TTL值必须小于或等于最大值", + "cache_ttl_min_override_desc": "缓存 DNS 响应时,延长从上游服务器接收到的 TTL 值 (秒)。", + "cache_ttl_max_override_desc": "设定 DNS 缓存条目的最大 TTL 值(秒)。", + "ttl_cache_validation": "最小缓存 TTL 值必须小于或等于最大值", "cache_optimistic": "乐观缓存", "cache_optimistic_desc": "即使条目已过期,也让 AdGuard Home 从缓存中响应,并尝试刷新它们。", "filter_category_general": "常规", @@ -624,9 +629,9 @@ "adg_will_drop_dns_queries": "AdGuard Home 会终止所有来自此客户端的DNS查询。", "filter_allowlist": "警告:此操作将把规则 \"{{disallowed_rule}}\" 排除在允许客户端的列表之外。", "last_rule_in_allowlist": "无法禁止此客户端,因为排除 “{{disallowed_rule}}” 规则将禁用“允许客户端”的列表。", - "experimental": "实验性的", "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/__locales/zh-hk.json b/client/src/__locales/zh-hk.json index e0ffb3e3..99028a59 100644 --- a/client/src/__locales/zh-hk.json +++ b/client/src/__locales/zh-hk.json @@ -38,12 +38,18 @@ "form_error_required": "必要欄位", "form_error_ip4_format": "無效的 IPv4 格式", "form_error_ip6_format": "無效的 IPv6 格式", + "form_error_ip4_range_start_format": "無效的 IPv4 範圍起始位址", + "form_error_ip4_range_end_format": "無效的 IPv4 範圍結束位址", + "form_error_ip4_gateway_format": "閘道的 IPv4 位址無效", "form_error_ip_format": "無效的 IP 位址", "form_error_mac_format": "無效的 「MAC 位址」格式", "form_error_client_id_format": "無效的「客戶端 ID」格式", "form_error_server_name": "無效伺服器名稱", "form_error_subnet": "子網路 \"{{cidr}}\" 不包含 IP 位址 \"{{ip}}\"", "form_error_positive": "數值必須大於 0", + "out_of_range_error": "必須介於 \"{{start}}\" - \"{{end}}\" 範圍之外", + "lower_range_start_error": "必須小於起始值", + "greater_range_start_error": "必須大於起始值", "dhcp_form_gateway_input": "閘道 IP 位址", "dhcp_form_subnet_input": "子網路遮罩", "dhcp_form_range_title": "IP 位址範圍", @@ -608,6 +614,5 @@ "original_response": "原始回應", "click_to_view_queries": "按一下以檢視查詢結果", "port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題的說明", - "adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。", - "experimental": "實驗性" + "adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。" } diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 0757f08d..3f4cdef2 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -1,7 +1,7 @@ { "client_settings": "用戶端設定", - "example_upstream_reserved": "您可<0>對於特定的網域明確指定 DNS 上游", - "example_upstream_comment": "您可明確指定註解", + "example_upstream_reserved": "<0>供特定的網域之上游;", + "example_upstream_comment": "註解。", "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。", "parallel_requests": "並行的請求", "load_balancing": "負載平衡", @@ -9,7 +9,7 @@ "bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器", "bootstrap_dns_desc": "自我啟動(Bootstrap)DNS 伺服器被用於解析您明確指定作為上游的 DoH/DoT 解析器之 IP 位址。", "local_ptr_title": "私人反向的 DNS 伺服器", - "local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析含私人 IP 位址的用戶端之主機名稱,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,除 AdGuard Home 它本身的位址之外,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。", + "local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。", "local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。", "local_ptr_no_default_resolver": "AdGuard Home 無法為此系統決定合適的私人反向的 DNS 解析器。", "local_ptr_placeholder": "每行輸入一個伺服器位址", @@ -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": "必須大於起始範圍", @@ -67,7 +68,7 @@ "dhcp_table_hostname": "主機名稱", "dhcp_table_expires": "到期", "dhcp_warning": "如果您無論如何想要啟用動態主機設定協定(DHCP)伺服器,確保在您的網路中無其它現行的 DHCP 伺服器,因為對於該網路上的裝置,這可能破壞其網際網路連線!", - "dhcp_error": "AdGuard Home 無法確定於該網路上是否有另外現行的動態主機設定協定(DHCP)伺服器。", + "dhcp_error": "AdGuard Home 無法確定於該網路上是否有另外現行的動態主機設定協定(DHCP)伺服器", "dhcp_static_ip_error": "為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。AdGuard Home 未能確定此網路介面是否被配置使用靜態 IP 位址。請手動地設定靜態 IP 位址。", "dhcp_dynamic_ip_found": "您的系統使用動態 IP 位址配置供介面 <0>{{interfaceName}}。為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。您現行的 IP 位址為 <0>{{ipAddress}}。如果您按\"啟用 DHCP 伺服器\" 按鈕,AdGuard Home 將自動地設定此 IP 位址作為靜態。", "dhcp_lease_added": "靜態租約 \"{{key}}\" 被成功地加入", @@ -197,28 +198,31 @@ "enter_valid_blocklist": "輸入一個到該封鎖清單之有效的網址。", "enter_valid_allowlist": "輸入一個到該允許清單之有效的網址。", "form_error_url_format": "無效的網址格式", - "form_error_url_or_path_format": "該清單之網址或絕對的路徑為無效的", + "form_error_url_or_path_format": "該清單之無效的網址或絕對的路徑", "custom_filter_rules": "自訂的過濾規則", "custom_filter_rules_hint": "於一行上輸入一項規則。您可使用廣告封鎖規則或主機檔案語法。", "system_host_files": "系統主機檔案", "examples_title": "範例", - "example_meaning_filter_block": "封鎖至 example.org 網域及其所有的子網域之存取", - "example_meaning_filter_whitelist": "解除封鎖至 example.org 網域及其所有的子網域之存取", - "example_meaning_host_block": "AdGuard Home 現在將對 example.org 網域(但非其子網域)返回 127.0.0.1 位址。", - "example_comment": "! 看,一個註解", - "example_comment_meaning": "只是一個註解", - "example_comment_hash": "# 也是一個註解", - "example_regex_meaning": "封鎖至與該已明確指定的規則運算式(Regular Expression)相符的網域之存取", - "example_upstream_regular": "一般的 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": "您可使用關於 <1>DNSCrypt 或 <2>DNS-over-HTTPS 解析器之 <0>DNS 戳記", - "example_upstream_tcp": "一般的 DNS(透過 TCP)", + "example_meaning_filter_block": "封鎖至 example.org 網域及其所有的子網域之存取;", + "example_meaning_filter_whitelist": "解除封鎖至 example.org 網域及其所有的子網域之存取;", + "example_meaning_host_block": "對 example.org(但非對其子網域)以 127.0.0.1 回覆;", + "example_comment": "! 看,一個註解。", + "example_comment_meaning": "只是一個註解;", + "example_comment_hash": "# 也是一個註解。", + "example_regex_meaning": "封鎖至與該已明確指定的規則運算式(Regular Expression)相符的網域之存取。", + "example_upstream_regular": "常規 DNS(透過 UDP);", + "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": "關於 <1>DNSCrypt 或 <2>DNS-over-HTTPS 解析器之 <0>DNS 戳記;", + "example_upstream_tcp": "常規 DNS(透過 TCP);", + "example_upstream_tcp_hostname": "常規 DNS(透過 TCP,主機名稱);", "all_lists_up_to_date_toast": "所有的清單已是最新的", "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": "不允許此用戶端", @@ -259,7 +263,7 @@ "query_log_strict_search": "使用雙引號於嚴謹的搜尋", "query_log_retention_confirm": "您確定您想要更改查詢記錄保留嗎?如果您減少該間隔值,某些資料將被丟失", "anonymize_client_ip": "將用戶端 IP 匿名", - "anonymize_client_ip_desc": "不要在記錄和統計資料中儲存用戶端之完整的 IP 位址", + "anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡", "dns_config": "DNS 伺服器配置", "dns_cache_config": "DNS 快取配置", "dns_cache_config_desc": "於此您可配置 DNS 快取", @@ -277,7 +281,7 @@ "dns_over_quic": "DNS-over-QUIC", "client_id": "用戶端 ID", "client_id_placeholder": "輸入用戶端 ID", - "client_id_desc": "不同的用戶端可根據特殊的用戶端 ID 被識別。<0>於此,您可了解更多關於如何識別用戶端。", + "client_id_desc": "用戶端可根據用戶端 ID 被識別。於此,了解更多關於如何識別用戶端。", "download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig", "download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig", "download_mobileconfig": "下載配置檔案", @@ -285,7 +289,7 @@ "form_enter_rate_limit": "輸入速率限制", "rate_limit": "速率限制", "edns_enable": "啟用對於 DNS 的擴充機制(EDNS)用戶端子網路", - "edns_cs_desc": "傳送用戶端的子網路到該 DNS 伺服器。", + "edns_cs_desc": "新增對於 DNS 的擴充機制(EDNS)用戶端子網路選項到上游的請求,並在查詢記錄中記錄由用戶端傳送的數值。", "rate_limit_desc": "每個用戶端被允許的每秒請求之數量。設定它為 0 表示無限制。", "blocking_ipv4_desc": "要被返回給已封鎖的 A 請求之 IP 位址", "blocking_ipv6_desc": "要被返回給已封鎖的 AAAA 請求之 IP 位址", @@ -334,12 +338,12 @@ "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_3": "在左側面板中,點擊\"變更介面卡設定\"。", + "install_devices_windows_list_4": "向您現行的連線點擊滑鼠右鍵,然後選擇內容。", "install_devices_windows_list_5": "在清單中找到\"網際網路通訊協定第 4 版(TCP/IPv4)\"[或用於 IPv6,\"網際網路通訊協定第 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_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 選單主畫面中,輕觸設定。", @@ -360,7 +364,7 @@ "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 連接埠", @@ -368,7 +372,7 @@ "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_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。", "encryption_certificates": "憑證", "encryption_certificates_desc": "為了使用加密,您需要提供有效的安全通訊端層(SSL)憑證鏈結供您的網域。於 <0>{{link}} 上您可取得免費的憑證或您可從受信任的憑證授權單位之一購買它。", "encryption_certificates_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)憑證。", @@ -389,7 +393,7 @@ "topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定。", "topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定。", "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼", - "form_error_port_unsafe": "此為不安全的連接埠", + "form_error_port_unsafe": "不安全的連接埠", "form_error_equal": "必須為不相等的", "form_error_password": "不相符的密碼", "reset_settings": "重置設定", @@ -405,8 +409,8 @@ "update_failed": "自動更新已失敗。請遵循這些步驟以手動地更新。", "manual_update": "請遵循這些步驟以手動地更新。", "processing_update": "請稍候,AdGuard Home 正被更新", - "clients_title": "用戶端", - "clients_desc": "配置被連線到 AdGuard Home 的裝置", + "clients_title": "持續性用戶端", + "clients_desc": "配置關於被連線到 AdGuard Home 的裝置之持續性用戶端記錄", "settings_global": "全域的", "settings_custom": "自訂的", "table_client": "用戶端", @@ -417,7 +421,7 @@ "client_edit": "編輯用戶端", "client_identifier": "識別碼", "ip_address": "IP 位址", - "client_identifier_desc": "用戶端可根據 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或特殊的用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此,您可了解更多關於如何識別用戶端。", + "client_identifier_desc": "用戶端可根據它們的 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此,了解更多關於如何識別用戶端。", "form_enter_ip": "輸入 IP", "form_enter_subnet_ip": "在子網路 \"{{cidr}}\" 中輸入一組 IP 位址", "form_enter_mac": "輸入媒體存取控制(MAC)", @@ -432,18 +436,18 @@ "clients_not_found": "無已發現之用戶端", "client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?", "list_confirm_delete": "您確定您想要刪除該清單嗎?", - "auto_clients_title": "用戶端(執行時期)", - "auto_clients_desc": "使用 AdGuard Home 但未被儲存在配置中之關於用戶端的資料", + "auto_clients_title": "執行時期用戶端", + "auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置", "access_title": "存取設定", - "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則。", + "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則", "access_allowed_title": "已允許的用戶端", - "access_allowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 ID 之清單。如果被配置,AdGuard Home 將接受僅來自這些用戶端的請求。", + "access_allowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 IDs 之清單。如果此清單有項目,AdGuard Home 將接受僅來自這些用戶端的請求。", "access_disallowed_title": "未被允許的用戶端", - "access_disallowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 IDs 之清單。如果被配置,AdGuard Home 將排除來自這些用戶端的請求。如果已允許的用戶端被配置,此欄位被忽略。", + "access_disallowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 IDs 之清單。如果此清單有項目,AdGuard Home 將排除來自這些用戶端的請求。如果在已允許的用戶端中有項目,此欄位被忽略。", "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 隱私", @@ -500,6 +504,7 @@ "interval_days": "{{count}} 日", "interval_days_plural": "{{count}} 日", "domain": "網域", + "ecs": "對於 DNS 的擴充機制(EDNS)用戶端子網路", "punycode": "國際化域名代碼(Punycode)", "answer": "回應", "filter_added_successfully": "該清單已被成功地加入", @@ -532,7 +537,7 @@ "netname": "網路名稱", "network": "網路", "descr": "說明", - "whois": "Whois", + "whois": "WHOIS", "filtering_rules_learn_more": "<0>了解更多有關創建您自己的主機(hosts)清單。", "blocked_by_response": "在回應過程中被正規名稱(CNAME)或 IP 封鎖", "blocked_by_cname_or_ip": "被正規名稱(CNAME)或 IP 封鎖", @@ -552,10 +557,10 @@ "autofix_warning_list": "它將執行這些任務:<0>撤銷系統 DNSStubListener <0>設定 DNS 伺服器位址為 127.0.0.1 <0>用 /run/systemd/resolve/resolv.conf 取代 /etc/resolv.conf 的符號連結目標 <0>停止 DNSStubListener(重新載入 systemd-resolved 服務)", "autofix_warning_result": "因此,預設下,來自您的系統之所有的 DNS 請求將被 AdGuard Home 處理。", "tags_title": "標記", - "tags_desc": "您可選擇對應該用戶端的標記。標記可被包括在過濾規則中並允許您更準確地套用它們。<0>了解更多", + "tags_desc": "您可選擇對應該用戶端的標記。包括在過濾規則中的標記以更準確地套用它們。<0>了解更多。", "form_select_tags": "選擇用戶端標記", "check_title": "檢查該過濾", - "check_desc": "檢查該主機名稱是否被過濾", + "check_desc": "檢查主機名稱是否被過濾。", "check": "檢查", "form_enter_host": "輸入主機名稱", "filtered_custom_rules": "被自訂的過濾規則過濾", @@ -598,15 +603,15 @@ "blocklist": "封鎖清單", "milliseconds_abbreviation": "ms", "cache_size": "快取大小", - "cache_size_desc": "DNS 快取大小(以位元組)", + "cache_size_desc": "DNS 快取大小(以位元組)。", "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": "設定最大的存活時間數值(秒)供在 DNS 快取中的項目", - "ttl_cache_validation": "最小的快取存活時間(TTL)數值必須小於或等於最大的數值", + "cache_ttl_min_override_desc": "當快取 DNS 回應時,延長從上游的伺服器收到的短存活時間數值(秒)。", + "cache_ttl_max_override_desc": "設定最大的存活時間數值(秒)供在 DNS 快取中的項目。", + "ttl_cache_validation": "最小的快取存活時間(TTL)覆寫必須小於或等於最大的", "cache_optimistic": "樂觀快取", "cache_optimistic_desc": "即使當項目為已到期的,從快取使 AdGuard Home 回覆,並還嘗試重新整理它們。", "filter_category_general": "一般的", @@ -622,11 +627,11 @@ "click_to_view_queries": "點擊以檢視查詢", "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}}” 規則將禁用“已允許用戶端”的清單。", - "experimental": "實驗性的", + "filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在已允許的用戶端的清單之外。", + "last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"已允許的用戶端\"清單,無法不允許此用戶端。", "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/actions/services.js b/client/src/actions/services.js index 1c95e3a1..650aa330 100644 --- a/client/src/actions/services.js +++ b/client/src/actions/services.js @@ -2,6 +2,21 @@ import { createAction } from 'redux-actions'; import apiClient from '../api/Api'; import { addErrorToast, addSuccessToast } from './toasts'; +export const getBlockedServicesAvailableServicesRequest = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_REQUEST'); +export const getBlockedServicesAvailableServicesFailure = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_FAILURE'); +export const getBlockedServicesAvailableServicesSuccess = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_SUCCESS'); + +export const getBlockedServicesAvailableServices = () => async (dispatch) => { + dispatch(getBlockedServicesAvailableServicesRequest()); + try { + const data = await apiClient.getBlockedServicesAvailableServices(); + dispatch(getBlockedServicesAvailableServicesSuccess(data)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getBlockedServicesAvailableServicesFailure()); + } +}; + export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST'); export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE'); export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS'); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 1f6b2832..d5693bfe 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -481,10 +481,17 @@ class Api { } // Blocked services + BLOCKED_SERVICES_SERVICES = { path: 'blocked_services/services', method: 'GET' }; + BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' }; BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' }; + getBlockedServicesAvailableServices() { + const { path, method } = this.BLOCKED_SERVICES_SERVICES; + return this.makeRequest(path, method); + } + getBlockedServices() { const { path, method } = this.BLOCKED_SERVICES_LIST; return this.makeRequest(path, method); 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 =
@@ -101,6 +103,7 @@ Form.propTypes = { ip: PropTypes.string.isRequired, hostname: PropTypes.string.isRequired, cidr: PropTypes.string.isRequired, + gatewayIp: PropTypes.string, }), pristine: PropTypes.bool.isRequired, handleSubmit: PropTypes.func.isRequired, diff --git a/client/src/components/Settings/Dhcp/StaticLeases/Modal.js b/client/src/components/Settings/Dhcp/StaticLeases/Modal.js index 8ad0f009..0baf487e 100644 --- a/client/src/components/Settings/Dhcp/StaticLeases/Modal.js +++ b/client/src/components/Settings/Dhcp/StaticLeases/Modal.js @@ -13,6 +13,7 @@ const Modal = ({ cidr, rangeStart, rangeEnd, + gatewayIp, }) => { const dispatch = useDispatch(); @@ -42,6 +43,7 @@ const Modal = ({ cidr, rangeStart, rangeEnd, + gatewayIp, }} onSubmit={handleSubmit} processingAdding={processingAdding} @@ -61,6 +63,7 @@ Modal.propTypes = { cidr: PropTypes.string.isRequired, rangeStart: PropTypes.string, rangeEnd: PropTypes.string, + gatewayIp: PropTypes.string, }; export default withTranslation()(Modal); diff --git a/client/src/components/Settings/Dhcp/StaticLeases/index.js b/client/src/components/Settings/Dhcp/StaticLeases/index.js index bdd7cec5..2374f044 100644 --- a/client/src/components/Settings/Dhcp/StaticLeases/index.js +++ b/client/src/components/Settings/Dhcp/StaticLeases/index.js @@ -24,6 +24,7 @@ const StaticLeases = ({ cidr, rangeStart, rangeEnd, + gatewayIp, }) => { const [t] = useTranslation(); const dispatch = useDispatch(); @@ -70,6 +71,8 @@ const StaticLeases = ({ Header: actions_table_header, accessor: 'actions', maxWidth: 150, + sortable: false, + resizable: false, // eslint-disable-next-line react/display-name Cell: (row) => { const { ip, mac, hostname } = row.original; @@ -104,6 +107,7 @@ const StaticLeases = ({ cidr={cidr} rangeStart={rangeStart} rangeEnd={rangeEnd} + gatewayIp={gatewayIp} /> ); @@ -117,6 +121,7 @@ StaticLeases.propTypes = { cidr: PropTypes.string.isRequired, rangeStart: PropTypes.string, rangeEnd: PropTypes.string, + gatewayIp: PropTypes.string, }; cellWrap.propTypes = { diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index a84e0a93..bd3a45e3 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -278,6 +278,7 @@ const Dhcp = () => { cidr={cidr} rangeStart={dhcp?.values?.v4?.range_start} rangeEnd={dhcp?.values?.v4?.range_end} + gatewayIp={dhcp?.values?.v4?.gateway_ip} />
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 submitValues = this.getSubmitValues(values); - this.props.validateTlsConfig(submitValues); + if (submitValues.enabled) { + this.props.validateTlsConfig(submitValues); + } }, DEBOUNCE_TIMEOUT); getInitialValues = (data) => { diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 4efb0868..3fd560f9 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -113,6 +113,7 @@ width: 30px; height: 30px; background-color: transparent; + overflow: hidden; } .btn-icon--green { diff --git a/client/src/components/ui/Footer.js b/client/src/components/ui/Footer.js index 7abe3378..393e16fe 100644 --- a/client/src/components/ui/Footer.js +++ b/client/src/components/ui/Footer.js @@ -44,7 +44,7 @@ const Footer = () => { 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.css b/client/src/components/ui/Icons.css index 73f4c864..929f4725 100644 --- a/client/src/components/ui/Icons.css +++ b/client/src/components/ui/Icons.css @@ -1,7 +1,6 @@ .icons { display: inline-block; vertical-align: middle; - height: 100%; } .icon--24 { 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/helpers.js b/client/src/helpers/helpers.js index 2546d5b9..a7bf9485 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -76,6 +76,7 @@ export const normalizeLogs = (logs) => logs.map((log) => { original_answer, upstream, cached, + ecs, } = log; const { name: domain, unicode_name: unicodeName, type } = question; @@ -118,6 +119,7 @@ export const normalizeLogs = (logs) => logs.map((log) => { elapsedMs, upstream, cached, + ecs, }; }); @@ -691,8 +693,8 @@ export const replaceZeroWithEmptyString = (value) => (parseInt(value, 10) === 0 * @returns {string} */ export const getLogsUrlParams = (search, response_status) => `?${queryString.stringify({ - search, - response_status, + search: search || undefined, + response_status: response_status || undefined, })}`; export const processContent = ( diff --git a/client/src/helpers/trackers/trackers.js b/client/src/helpers/trackers/trackers.js index 7af6dcb4..5327d18e 100644 --- a/client/src/helpers/trackers/trackers.js +++ b/client/src/helpers/trackers/trackers.js @@ -1,4 +1,5 @@ import whotracksmeDb from './whotracksme.json'; +import whotracksmeWebsites from './whotracksme_web.json'; import adguardDb from './adguard.json'; import { REPOSITORY } from '../constants'; @@ -20,6 +21,22 @@ export const sources = { ADGUARD: 2, }; +/** + * Gets link to tracker page on whotracks.me. + * + * @param trackerId + * @return {string} + */ +const getWhotracksmeUrl = (trackerId) => { + const websiteId = whotracksmeWebsites.websites[trackerId]; + if (websiteId) { + // Overrides links to websites. + return `https://whotracks.me/websites/${websiteId}.html`; + } + + return `https://whotracks.me/trackers/${trackerId}.html`; +}; + /** * Gets the source metadata for the specified tracker * @param {TrackerData} trackerData tracker data @@ -33,7 +50,7 @@ export const getSourceData = (trackerData) => { if (trackerData.source === sources.WHOTRACKSME) { return { name: 'Whotracks.me', - url: `https://whotracks.me/trackers/${trackerData.id}.html`, + url: getWhotracksmeUrl(trackerData.id), }; } if (trackerData.source === sources.ADGUARD) { diff --git a/client/src/helpers/trackers/whotracksme_web.json b/client/src/helpers/trackers/whotracksme_web.json new file mode 100644 index 00000000..8123fe1b --- /dev/null +++ b/client/src/helpers/trackers/whotracksme_web.json @@ -0,0 +1,6 @@ +{ + "timeUpdated": "2021-12-19T13:50:00.512Z", + "websites": { + "netflix": "netflix.com" + } +} 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/client/src/install/Setup/Settings.js b/client/src/install/Setup/Settings.js index ce128fb8..234d345a 100644 --- a/client/src/install/Setup/Settings.js +++ b/client/src/install/Setup/Settings.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import { Field, reduxForm, formValueSelector } from 'redux-form'; import { Trans, withTranslation } from 'react-i18next'; import flow from 'lodash/flow'; +import i18n from 'i18next'; import Controls from './Controls'; import AddressList from './AddressList'; @@ -31,10 +32,10 @@ const renderInterfaces = (interfaces) => Object.values(interfaces) if (option && ip_addresses?.length > 0) { const ip = getInterfaceIp(option); - const isDown = flags?.includes('down'); + const isUp = flags?.includes('up'); - return ; } diff --git a/client2/src/lib/apis/blockedServices.ts b/client2/src/lib/apis/blockedServices.ts index 381a236d..7daa3344 100644 --- a/client2/src/lib/apis/blockedServices.ts +++ b/client2/src/lib/apis/blockedServices.ts @@ -1,6 +1,18 @@ // This file was autogenerated. Please do not change. // All changes will be overwrited on commit. export default class BlockedServicesApi { + static async blockedServicesAvailableServices(): Promise { + return await fetch(`/control/blocked_services/services`, { + method: 'GET', + }).then(async (res) => { + if (res.status === 200) { + return res.json(); + } else { + return new Error(String(res.status)); + } + }) + } + static async blockedServicesList(): Promise { return await fetch(`/control/blocked_services/list`, { method: 'GET', 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 00182235..018afe80 100644 --- a/go.mod +++ b/go.mod @@ -1,66 +1,68 @@ module github.com/AdguardTeam/AdGuardHome -go 1.17 +go 1.18 require ( - github.com/AdguardTeam/dnsproxy v0.41.1 - github.com/AdguardTeam/golibs v0.10.5 - github.com/AdguardTeam/urlfilter v0.15.2 + 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/fsnotify/fsnotify v1.5.1 - github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 - github.com/google/go-cmp v0.5.6 + github.com/dimfeld/httptreemux/v5 v5.4.0 + github.com/fsnotify/fsnotify v1.5.4 + 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-20211214070828-5297eed8f489 - github.com/kardianos/service v1.2.0 - github.com/lucas-clemente/quic-go v0.25.0 - github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 - github.com/mdlayher/netlink v1.5.0 - github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b - github.com/miekg/dns v1.1.45 - github.com/satori/go.uuid v1.2.0 - github.com/stretchr/testify v1.7.0 + 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.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. 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-20211215153901-e495a2d5b3d3 - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e + 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 ) require ( - github.com/BurntSushi/toml v0.4.1 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/ameshkov/dnsstamps v1.0.3 // indirect - github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect + github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect 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 v0.0.0-20200817173448-b6b71def0850 // indirect - github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect - github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect - github.com/mdlayher/socket v0.1.1 // 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.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 github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect 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-20210528151154-e40b768296a7 // indirect - golang.org/x/mod v0.5.1 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // 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.8 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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 - honnef.co/go/tools v0.2.2 // indirect ) diff --git a/go.sum b/go.sum index 99f20fd1..240544dc 100644 --- a/go.sum +++ b/go.sum @@ -7,20 +7,19 @@ 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.41.1 h1:sDWami83ZNp0XNdWsLECwIX/hPI5UnVrotRtPnrgDuo= -github.com/AdguardTeam/dnsproxy v0.41.1/go.mod h1:PZ9l22h3Er+5mxFQB7oHZMTvx+aa9R6LbzA/ikXQlS0= +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.3/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.10.5 h1:4/nl1yIBJOv5luVu9SURW8LfgOjI3zQ2moIUy/1k0y4= -github.com/AdguardTeam/golibs v0.10.5/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.15.2 h1:LZGgrm4l4Ys9eAqB+UUmZfiC6vHlDlYFhx0WXqo6LtQ= -github.com/AdguardTeam/urlfilter v0.15.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= +github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg= +github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -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/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= @@ -35,15 +34,13 @@ github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaE 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 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g= -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= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -52,22 +49,23 @@ 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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 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 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -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= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 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= @@ -95,10 +93,11 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.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.4/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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +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,87 +117,69 @@ 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-20211214070828-5297eed8f489 h1:jhdHqd7DxBrzfuFSoPxjD6nUVaV/1RIn9aHA0WCf/as= -github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489/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 v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= -github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +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= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= -github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw= -github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs= -github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA= -github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= -github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo= -github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ= -github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= -github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= +github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= -github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc= -github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= +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 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= -github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= -github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk= -github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM= -github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= +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.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 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= -github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= -github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8= -github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE= -github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= -github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= +github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE= +github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og= github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= -github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8= -github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= -github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= -github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= -github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= -github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q= -github.com/mdlayher/netlink v1.5.0 h1:r4fa439+SsMarM0rMONU3iSshSV3ArVqJl6H/zjrhh4= -github.com/mdlayher/netlink v1.5.0/go.mod h1:1Kr8BBFxGyUyNmztC9WLOayqYVAd2wsgOZm18nqGuzQ= +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/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= -github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g= -github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI= +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.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= -github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +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= @@ -232,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= @@ -270,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= @@ -281,14 +261,12 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= -github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4= -github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME= +github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= 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.0/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= @@ -301,9 +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-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/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= @@ -311,8 +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.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +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= @@ -324,6 +304,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -337,24 +318,17 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/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-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/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= @@ -366,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= @@ -397,36 +372,28 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/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= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 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-20220412211240-33da011f77ad/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= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -447,16 +414,13 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn 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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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= @@ -502,15 +466,12 @@ 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= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= -honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= 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/arpdb.go b/internal/aghnet/arpdb.go new file mode 100644 index 00000000..4909af5f --- /dev/null +++ b/internal/aghnet/arpdb.go @@ -0,0 +1,211 @@ +package aghnet + +import ( + "bufio" + "bytes" + "fmt" + "net" + "sync" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" +) + +// ARPDB: The Network Neighborhood Database + +// ARPDB stores and refreshes the network neighborhood reported by ARP (Address +// Resolution Protocol). +type ARPDB interface { + // Refresh updates the stored data. It must be safe for concurrent use. + Refresh() (err error) + + // Neighbors returnes the last set of data reported by ARP. Both the method + // and it's result must be safe for concurrent use. + Neighbors() (ns []Neighbor) +} + +// NewARPDB returns the ARPDB properly initialized for the OS. +func NewARPDB() (arp ARPDB) { + return newARPDB() +} + +// Empty ARPDB implementation + +// EmptyARPDB is the ARPDB implementation that does nothing. +type EmptyARPDB struct{} + +// type check +var _ ARPDB = EmptyARPDB{} + +// Refresh implements the ARPDB interface for EmptyARPContainer. It does +// nothing and always returns nil error. +func (EmptyARPDB) Refresh() (err error) { return nil } + +// Neighbors implements the ARPDB interface for EmptyARPContainer. It always +// returns nil. +func (EmptyARPDB) Neighbors() (ns []Neighbor) { return nil } + +// ARPDB Helper Types + +// Neighbor is the pair of IP address and MAC address reported by ARP. +type Neighbor struct { + // Name is the hostname of the neighbor. Empty name is valid since not each + // implementation of ARP is able to retrieve that. + Name string + + // IP contains either IPv4 or IPv6. + IP net.IP + + // MAC contains the hardware address. + MAC net.HardwareAddr +} + +// Clone returns the deep copy of n. +func (n Neighbor) Clone() (clone Neighbor) { + return Neighbor{ + Name: n.Name, + IP: netutil.CloneIP(n.IP), + MAC: netutil.CloneMAC(n.MAC), + } +} + +// neighs is the helper type that stores neighbors to avoid copying its methods +// among all the ARPDB implementations. +type neighs struct { + mu *sync.RWMutex + ns []Neighbor +} + +// len returns the length of the neighbors slice. It's safe for concurrent use. +func (ns *neighs) len() (l int) { + ns.mu.RLock() + defer ns.mu.RUnlock() + + return len(ns.ns) +} + +// clone returns a deep copy of the underlying neighbors slice. It's safe for +// concurrent use. +func (ns *neighs) clone() (cloned []Neighbor) { + ns.mu.RLock() + defer ns.mu.RUnlock() + + cloned = make([]Neighbor, len(ns.ns)) + for i, n := range ns.ns { + cloned[i] = n.Clone() + } + + return cloned +} + +// reset replaces the underlying slice with the new one. It's safe for +// concurrent use. +func (ns *neighs) reset(with []Neighbor) { + ns.mu.Lock() + defer ns.mu.Unlock() + + ns.ns = with +} + +// Command ARPDB + +// parseNeighsFunc parses the text from sc as if it'd be an output of some +// ARP-related command. lenHint is a hint for the size of the allocated slice +// of Neighbors. +type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor) + +// cmdARPDB is the implementation of the ARPDB that uses command line to +// retrieve data. +type cmdARPDB struct { + parse parseNeighsFunc + ns *neighs + cmd string + args []string +} + +// type check +var _ ARPDB = (*cmdARPDB)(nil) + +// Refresh implements the ARPDB interface for *cmdARPDB. +func (arp *cmdARPDB) Refresh() (err error) { + defer func() { err = errors.Annotate(err, "cmd arpdb: %w") }() + + code, out, err := aghosRunCommand(arp.cmd, arp.args...) + if err != nil { + return fmt.Errorf("running command: %w", err) + } else if code != 0 { + return fmt.Errorf("running command: unexpected exit code %d", code) + } + + sc := bufio.NewScanner(bytes.NewReader(out)) + ns := arp.parse(sc, arp.ns.len()) + if err = sc.Err(); err != nil { + // TODO(e.burkov): This error seems unreachable. Investigate. + return fmt.Errorf("scanning the output: %w", err) + } + + arp.ns.reset(ns) + + return nil +} + +// Neighbors implements the ARPDB interface for *cmdARPDB. +func (arp *cmdARPDB) Neighbors() (ns []Neighbor) { + return arp.ns.clone() +} + +// Composite ARPDB + +// arpdbs is the ARPDB that combines several ARPDB implementations and +// consequently switches between those. +type arpdbs struct { + // arps is the set of ARPDB implementations to range through. + arps []ARPDB + neighs +} + +// newARPDBs returns a properly initialized *arpdbs. It begins refreshing from +// the first of arps. +func newARPDBs(arps ...ARPDB) (arp *arpdbs) { + return &arpdbs{ + arps: arps, + neighs: neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + } +} + +// type check +var _ ARPDB = (*arpdbs)(nil) + +// Refresh implements the ARPDB interface for *arpdbs. +func (arp *arpdbs) Refresh() (err error) { + var errs []error + + for _, a := range arp.arps { + err = a.Refresh() + if err != nil { + errs = append(errs, err) + + continue + } + + arp.reset(a.Neighbors()) + + return nil + } + + if len(errs) > 0 { + err = errors.List("each arpdb failed", errs...) + } + + return err +} + +// Neighbors implements the ARPDB interface for *arpdbs. +// +// TODO(e.burkov): Think of a way to avoid cloning the slice twice. +func (arp *arpdbs) Neighbors() (ns []Neighbor) { + return arp.clone() +} diff --git a/internal/aghnet/arpdb_bsd.go b/internal/aghnet/arpdb_bsd.go new file mode 100644 index 00000000..26ac7758 --- /dev/null +++ b/internal/aghnet/arpdb_bsd.go @@ -0,0 +1,76 @@ +//go:build darwin || freebsd +// +build darwin freebsd + +package aghnet + +import ( + "bufio" + "net" + "strings" + "sync" + + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil" +) + +func newARPDB() (arp *cmdARPDB) { + return &cmdARPDB{ + parse: parseArpA, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + cmd: "arp", + // Use -n flag to avoid resolving the hostnames of the neighbors. By + // default ARP attempts to resolve the hostnames via DNS. See man 8 + // arp. + // + // See also https://github.com/AdguardTeam/AdGuardHome/issues/3157. + args: []string{"-a", "-n"}, + } +} + +// parseArpA parses the output of the "arp -a -n" command on macOS and FreeBSD. +// The expected input format: +// +// host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet] +// +func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + + fields := strings.Fields(ln) + if len(fields) < 4 { + continue + } + + n := Neighbor{} + + if ipStr := fields[1]; len(ipStr) < 2 { + continue + } else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil { + continue + } else { + n.IP = ip + } + + hwStr := fields[3] + if mac, err := net.ParseMAC(hwStr); err != nil { + continue + } else { + n.MAC = mac + } + + host := fields[0] + if err := netutil.ValidateDomainName(host); err != nil { + log.Debug("parsing arp output: %s", err) + } else { + n.Name = host + } + + ns = append(ns, n) + } + + return ns +} diff --git a/internal/aghnet/arpdb_bsd_test.go b/internal/aghnet/arpdb_bsd_test.go new file mode 100644 index 00000000..3404af69 --- /dev/null +++ b/internal/aghnet/arpdb_bsd_test.go @@ -0,0 +1,31 @@ +//go:build darwin || freebsd +// +build darwin freebsd + +package aghnet + +import ( + "net" +) + +const arpAOutput = ` +invalid.mac (1.2.3.4) at 12:34:56:78:910 on el0 ifscope [ethernet] +invalid.ip (1.2.3.4.5) at ab:cd:ef:ab:cd:12 on ek0 ifscope [ethernet] +invalid.fmt 1 at 12:cd:ef:ab:cd:ef on er0 ifscope [ethernet] +hostname.one (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet] +hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [ethernet] +? (::1234) at aa:bb:cc:dd:ee:ff on ej0 expires in 1918 seconds [ethernet] +` + +var wantNeighs = []Neighbor{{ + Name: "hostname.one", + IP: net.IPv4(192, 168, 1, 2), + MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}, +}, { + Name: "hostname.two", + IP: net.ParseIP("::ffff:ffff"), + MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB}, +}, { + Name: "", + IP: net.ParseIP("::1234"), + MAC: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, +}} diff --git a/internal/aghnet/arpdb_linux.go b/internal/aghnet/arpdb_linux.go new file mode 100644 index 00000000..9cc38906 --- /dev/null +++ b/internal/aghnet/arpdb_linux.go @@ -0,0 +1,243 @@ +//go:build linux +// +build linux + +package aghnet + +import ( + "bufio" + "fmt" + "io/fs" + "net" + "strings" + "sync" + + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/stringutil" +) + +func newARPDB() (arp *arpdbs) { + // Use the common storage among the implementations. + ns := &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + } + + var parseF parseNeighsFunc + if aghos.IsOpenWrt() { + parseF = parseArpAWrt + } else { + parseF = parseArpA + } + + return newARPDBs( + // Try /proc/net/arp first. + &fsysARPDB{ + ns: ns, + fsys: rootDirFS, + filename: "proc/net/arp", + }, + // Then, try "arp -a -n". + &cmdARPDB{ + parse: parseF, + ns: ns, + cmd: "arp", + // Use -n flag to avoid resolving the hostnames of the neighbors. + // By default ARP attempts to resolve the hostnames via DNS. See + // man 8 arp. + // + // See also https://github.com/AdguardTeam/AdGuardHome/issues/3157. + args: []string{"-a", "-n"}, + }, + // Finally, try "ip neigh". + &cmdARPDB{ + parse: parseIPNeigh, + ns: ns, + cmd: "ip", + args: []string{"neigh"}, + }, + ) +} + +// fsysARPDB accesses the ARP cache file to update the database. +type fsysARPDB struct { + ns *neighs + fsys fs.FS + filename string +} + +// type check +var _ ARPDB = (*fsysARPDB)(nil) + +// Refresh implements the ARPDB interface for *fsysARPDB. +func (arp *fsysARPDB) Refresh() (err error) { + var f fs.File + f, err = arp.fsys.Open(arp.filename) + if err != nil { + return fmt.Errorf("opening %q: %w", arp.filename, err) + } + + sc := bufio.NewScanner(f) + // Skip the header. + if !sc.Scan() { + return nil + } else if err = sc.Err(); err != nil { + return err + } + + ns := make([]Neighbor, 0, arp.ns.len()) + for sc.Scan() { + ln := sc.Text() + fields := stringutil.SplitTrimmed(ln, " ") + if len(fields) != 6 { + continue + } + + n := Neighbor{} + if n.IP = net.ParseIP(fields[0]); n.IP == nil || n.IP.IsUnspecified() { + continue + } else if n.MAC, err = net.ParseMAC(fields[3]); err != nil { + continue + } + + ns = append(ns, n) + } + + arp.ns.reset(ns) + + return nil +} + +// Neighbors implements the ARPDB interface for *fsysARPDB. +func (arp *fsysARPDB) Neighbors() (ns []Neighbor) { + return arp.ns.clone() +} + +// parseArpAWrt parses the output of the "arp -a -n" command on OpenWrt. The +// expected input format: +// +// IP address HW type Flags HW address Mask Device +// 192.168.11.98 0x1 0x2 5a:92:df:a9:7e:28 * wan +// +func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + if !sc.Scan() { + // Skip the header. + return + } + + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + + fields := strings.Fields(ln) + if len(fields) < 4 { + continue + } + + n := Neighbor{} + + if ip := net.ParseIP(fields[0]); ip == nil || n.IP.IsUnspecified() { + continue + } else { + n.IP = ip + } + + hwStr := fields[3] + if mac, err := net.ParseMAC(hwStr); err != nil { + log.Debug("parsing arp output: %s", err) + + continue + } else { + n.MAC = mac + } + + ns = append(ns, n) + } + + return ns +} + +// parseArpA parses the output of the "arp -a -n" command on Linux. The +// expected input format: +// +// hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3 +// +func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + + fields := strings.Fields(ln) + if len(fields) < 4 { + continue + } + + n := Neighbor{} + + if ipStr := fields[1]; len(ipStr) < 2 { + continue + } else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil { + continue + } else { + n.IP = ip + } + + hwStr := fields[3] + if mac, err := net.ParseMAC(hwStr); err != nil { + log.Debug("parsing arp output: %s", err) + + continue + } else { + n.MAC = mac + } + + host := fields[0] + if verr := netutil.ValidateDomainName(host); verr != nil { + log.Debug("parsing arp output: %s", verr) + } else { + n.Name = host + } + + ns = append(ns, n) + } + + return ns +} + +// parseIPNeigh parses the output of the "ip neigh" command on Linux. The +// expected input format: +// +// 192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE +// +func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + + fields := strings.Fields(ln) + if len(fields) < 5 { + continue + } + + n := Neighbor{} + + if ip := net.ParseIP(fields[0]); ip == nil { + continue + } else { + n.IP = ip + } + + if mac, err := net.ParseMAC(fields[4]); err != nil { + log.Debug("parsing arp output: %s", err) + + continue + } else { + n.MAC = mac + } + + ns = append(ns, n) + } + + return ns +} diff --git a/internal/aghnet/arpdb_linux_test.go b/internal/aghnet/arpdb_linux_test.go new file mode 100644 index 00000000..46d87150 --- /dev/null +++ b/internal/aghnet/arpdb_linux_test.go @@ -0,0 +1,102 @@ +//go:build linux +// +build linux + +package aghnet + +import ( + "net" + "sync" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const arpAOutputWrt = ` +IP address HW type Flags HW address Mask Device +1.2.3.4.5 0x1 0x2 aa:bb:cc:dd:ee:ff * wan +1.2.3.4 0x1 0x2 12:34:56:78:910 * wan +192.168.1.2 0x1 0x2 ab:cd:ef:ab:cd:ef * wan +::ffff:ffff 0x1 0x2 ef:cd:ab:ef:cd:ab * wan` + +const arpAOutput = ` +invalid.mac (1.2.3.4) at 12:34:56:78:910 on el0 ifscope [ethernet] +invalid.ip (1.2.3.4.5) at ab:cd:ef:ab:cd:12 on ek0 ifscope [ethernet] +invalid.fmt 1 at 12:cd:ef:ab:cd:ef on er0 ifscope [ethernet] +? (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet] +? (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 100 seconds [ethernet]` + +const ipNeighOutput = ` +1.2.3.4.5 dev enp0s3 lladdr aa:bb:cc:dd:ee:ff DELAY +1.2.3.4 dev enp0s3 lladdr 12:34:56:78:910 DELAY +192.168.1.2 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef DELAY +::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE` + +var wantNeighs = []Neighbor{{ + IP: net.IPv4(192, 168, 1, 2), + MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}, +}, { + IP: net.ParseIP("::ffff:ffff"), + MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB}, +}} + +func TestFSysARPDB(t *testing.T) { + require.NoError(t, fstest.TestFS(testdata, "proc_net_arp")) + + a := &fsysARPDB{ + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + fsys: testdata, + filename: "proc_net_arp", + } + + err := a.Refresh() + require.NoError(t, err) + + ns := a.Neighbors() + assert.Equal(t, wantNeighs, ns) +} + +func TestCmdARPDB_linux(t *testing.T) { + sh := mapShell{ + "arp -a": {err: nil, out: arpAOutputWrt, code: 0}, + "ip neigh": {err: nil, out: ipNeighOutput, code: 0}, + } + substShell(t, sh.RunCmd) + + t.Run("wrt", func(t *testing.T) { + a := &cmdARPDB{ + parse: parseArpAWrt, + cmd: "arp", + args: []string{"-a"}, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + } + + err := a.Refresh() + require.NoError(t, err) + + assert.Equal(t, wantNeighs, a.Neighbors()) + }) + + t.Run("ip_neigh", func(t *testing.T) { + a := &cmdARPDB{ + parse: parseIPNeigh, + cmd: "ip", + args: []string{"neigh"}, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + } + err := a.Refresh() + require.NoError(t, err) + + assert.Equal(t, wantNeighs, a.Neighbors()) + }) +} diff --git a/internal/aghnet/arpdb_openbsd.go b/internal/aghnet/arpdb_openbsd.go new file mode 100644 index 00000000..d5ec5fea --- /dev/null +++ b/internal/aghnet/arpdb_openbsd.go @@ -0,0 +1,73 @@ +//go:build openbsd +// +build openbsd + +package aghnet + +import ( + "bufio" + "net" + "strings" + "sync" + + "github.com/AdguardTeam/golibs/log" +) + +func newARPDB() (arp *cmdARPDB) { + return &cmdARPDB{ + parse: parseArpA, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + cmd: "arp", + // Use -n flag to avoid resolving the hostnames of the neighbors. By + // default ARP attempts to resolve the hostnames via DNS. See man 8 + // arp. + // + // See also https://github.com/AdguardTeam/AdGuardHome/issues/3157. + args: []string{"-a", "-n"}, + } +} + +// parseArpA parses the output of the "arp -a -n" command on OpenBSD. The +// expected input format: +// +// Host Ethernet Address Netif Expire Flags +// 192.168.1.1 ab:cd:ef:ab:cd:ef em0 19m59s +// +func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + // Skip the header. + if !sc.Scan() { + return nil + } + + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + + fields := strings.Fields(ln) + if len(fields) < 2 { + continue + } + + n := Neighbor{} + + if ip := net.ParseIP(fields[0]); ip == nil { + continue + } else { + n.IP = ip + } + + if mac, err := net.ParseMAC(fields[1]); err != nil { + log.Debug("parsing arp output: %s", err) + + continue + } else { + n.MAC = mac + } + + ns = append(ns, n) + } + + return ns +} diff --git a/internal/aghnet/arpdb_openbsd_test.go b/internal/aghnet/arpdb_openbsd_test.go new file mode 100644 index 00000000..915c17ff --- /dev/null +++ b/internal/aghnet/arpdb_openbsd_test.go @@ -0,0 +1,24 @@ +//go:build openbsd +// +build openbsd + +package aghnet + +import ( + "net" +) + +const arpAOutput = ` +Host Ethernet Address Netif Expire Flags +1.2.3.4.5 aa:bb:cc:dd:ee:ff em0 permanent +1.2.3.4 12:34:56:78:910 em0 permanent +192.168.1.2 ab:cd:ef:ab:cd:ef em0 19m56s +::ffff:ffff ef:cd:ab:ef:cd:ab em0 permanent l +` + +var wantNeighs = []Neighbor{{ + IP: net.IPv4(192, 168, 1, 2), + MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}, +}, { + IP: net.ParseIP("::ffff:ffff"), + MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB}, +}} diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go new file mode 100644 index 00000000..d6971448 --- /dev/null +++ b/internal/aghnet/arpdb_test.go @@ -0,0 +1,216 @@ +package aghnet + +import ( + "net" + "sync" + "testing" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewARPDB(t *testing.T) { + var a ARPDB + require.NotPanics(t, func() { a = NewARPDB() }) + + assert.NotNil(t, a) +} + +// TestARPDB is the mock implementation of ARPDB to use in tests. +type TestARPDB struct { + OnRefresh func() (err error) + OnNeighbors func() (ns []Neighbor) +} + +// Refresh implements the ARPDB interface for *TestARPDB. +func (arp *TestARPDB) Refresh() (err error) { + return arp.OnRefresh() +} + +// Neighbors implements the ARPDB interface for *TestARPDB. +func (arp *TestARPDB) Neighbors() (ns []Neighbor) { + return arp.OnNeighbors() +} + +func TestARPDBS(t *testing.T) { + knownIP := net.IP{1, 2, 3, 4} + knownMAC := net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF} + + succRefrCount, failRefrCount := 0, 0 + clnp := func() { + succRefrCount, failRefrCount = 0, 0 + } + + succDB := &TestARPDB{ + OnRefresh: func() (err error) { succRefrCount++; return nil }, + OnNeighbors: func() (ns []Neighbor) { + return []Neighbor{{Name: "abc", IP: knownIP, MAC: knownMAC}} + }, + } + failDB := &TestARPDB{ + OnRefresh: func() (err error) { failRefrCount++; return errors.Error("refresh failed") }, + OnNeighbors: func() (ns []Neighbor) { return nil }, + } + + t.Run("begin_with_success", func(t *testing.T) { + t.Cleanup(clnp) + + a := newARPDBs(succDB, failDB) + err := a.Refresh() + require.NoError(t, err) + + assert.Equal(t, 1, succRefrCount) + assert.Zero(t, failRefrCount) + assert.NotEmpty(t, a.Neighbors()) + }) + + t.Run("begin_with_fail", func(t *testing.T) { + t.Cleanup(clnp) + + a := newARPDBs(failDB, succDB) + err := a.Refresh() + require.NoError(t, err) + + assert.Equal(t, 1, succRefrCount) + assert.Equal(t, 1, failRefrCount) + assert.NotEmpty(t, a.Neighbors()) + }) + + t.Run("fail_only", func(t *testing.T) { + t.Cleanup(clnp) + + wantMsg := `each arpdb failed: 2 errors: "refresh failed", "refresh failed"` + + a := newARPDBs(failDB, failDB) + err := a.Refresh() + require.Error(t, err) + + testutil.AssertErrorMsg(t, wantMsg, err) + + assert.Equal(t, 2, failRefrCount) + assert.Empty(t, a.Neighbors()) + }) + + t.Run("fail_after_success", func(t *testing.T) { + t.Cleanup(clnp) + + shouldFail := false + unstableDB := &TestARPDB{ + OnRefresh: func() (err error) { + if shouldFail { + err = errors.Error("unstable failed") + } + shouldFail = !shouldFail + + return err + }, + OnNeighbors: func() (ns []Neighbor) { + if !shouldFail { + return failDB.OnNeighbors() + } + + return succDB.OnNeighbors() + }, + } + a := newARPDBs(unstableDB, succDB) + + // Unstable ARPDB should refresh successfully. + err := a.Refresh() + require.NoError(t, err) + + assert.Zero(t, succRefrCount) + assert.NotEmpty(t, a.Neighbors()) + + // Unstable ARPDB should fail and the succDB should be used. + err = a.Refresh() + require.NoError(t, err) + + assert.Equal(t, 1, succRefrCount) + assert.NotEmpty(t, a.Neighbors()) + + // Unstable ARPDB should refresh successfully again. + err = a.Refresh() + require.NoError(t, err) + + assert.Equal(t, 1, succRefrCount) + assert.NotEmpty(t, a.Neighbors()) + }) + + t.Run("empty", func(t *testing.T) { + a := newARPDBs() + require.NoError(t, a.Refresh()) + + assert.Empty(t, a.Neighbors()) + }) +} + +func TestCmdARPDB_arpa(t *testing.T) { + a := &cmdARPDB{ + cmd: "cmd", + parse: parseArpA, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + } + + t.Run("arp_a", func(t *testing.T) { + sh := theOnlyCmd("cmd", 0, arpAOutput, nil) + substShell(t, sh.RunCmd) + + err := a.Refresh() + require.NoError(t, err) + + assert.Equal(t, wantNeighs, a.Neighbors()) + }) + + t.Run("runcmd_error", func(t *testing.T) { + sh := theOnlyCmd("cmd", 0, "", errors.Error("can't run")) + substShell(t, sh.RunCmd) + + err := a.Refresh() + testutil.AssertErrorMsg(t, "cmd arpdb: running command: can't run", err) + }) + + t.Run("bad_code", func(t *testing.T) { + sh := theOnlyCmd("cmd", 1, "", nil) + substShell(t, sh.RunCmd) + + err := a.Refresh() + testutil.AssertErrorMsg(t, "cmd arpdb: running command: unexpected exit code 1", err) + }) + + t.Run("empty", func(t *testing.T) { + sh := theOnlyCmd("cmd", 0, "", nil) + substShell(t, sh.RunCmd) + + err := a.Refresh() + require.NoError(t, err) + + assert.Empty(t, a.Neighbors()) + }) +} + +func TestEmptyARPDB(t *testing.T) { + a := EmptyARPDB{} + + t.Run("refresh", func(t *testing.T) { + var err error + require.NotPanics(t, func() { + err = a.Refresh() + }) + + assert.NoError(t, err) + }) + + t.Run("neighbors", func(t *testing.T) { + var ns []Neighbor + require.NotPanics(t, func() { + ns = a.Neighbors() + }) + + assert.Empty(t, ns) + }) +} diff --git a/internal/aghnet/arpdb_windows.go b/internal/aghnet/arpdb_windows.go new file mode 100644 index 00000000..8d5431eb --- /dev/null +++ b/internal/aghnet/arpdb_windows.go @@ -0,0 +1,65 @@ +//go:build windows +// +build windows + +package aghnet + +import ( + "bufio" + "net" + "strings" + "sync" +) + +func newARPDB() (arp *cmdARPDB) { + return &cmdARPDB{ + parse: parseArpA, + ns: &neighs{ + mu: &sync.RWMutex{}, + ns: make([]Neighbor, 0), + }, + cmd: "arp", + args: []string{"/a"}, + } +} + +// parseArpA parses the output of the "arp /a" command on Windows. The expected +// input format (the first line is empty): +// +// +// Interface: 192.168.56.16 --- 0x7 +// Internet Address Physical Address Type +// 192.168.56.1 0a-00-27-00-00-00 dynamic +// 192.168.56.255 ff-ff-ff-ff-ff-ff static +// +func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { + ns = make([]Neighbor, 0, lenHint) + for sc.Scan() { + ln := sc.Text() + if ln == "" { + continue + } + + fields := strings.Fields(ln) + if len(fields) != 3 { + continue + } + + n := Neighbor{} + + if ip := net.ParseIP(fields[0]); ip == nil { + continue + } else { + n.IP = ip + } + + if mac, err := net.ParseMAC(fields[1]); err != nil { + continue + } else { + n.MAC = mac + } + + ns = append(ns, n) + } + + return ns +} diff --git a/internal/aghnet/arpdb_windows_test.go b/internal/aghnet/arpdb_windows_test.go new file mode 100644 index 00000000..ad88ff8e --- /dev/null +++ b/internal/aghnet/arpdb_windows_test.go @@ -0,0 +1,23 @@ +//go:build windows +// +build windows + +package aghnet + +import ( + "net" +) + +const arpAOutput = ` + +Interface: 192.168.1.1 --- 0x7 + Internet Address Physical Address Type + 192.168.1.2 ab-cd-ef-ab-cd-ef dynamic + ::ffff:ffff ef-cd-ab-ef-cd-ab static` + +var wantNeighs = []Neighbor{{ + IP: net.IPv4(192, 168, 1, 2), + MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}, +}, { + IP: net.ParseIP("::ffff:ffff"), + MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB}, +}} diff --git a/internal/aghnet/dhcp_unix.go b/internal/aghnet/dhcp_unix.go index fd2b9b93..4791d0e5 100644 --- a/internal/aghnet/dhcp_unix.go +++ b/internal/aghnet/dhcp_unix.go @@ -156,7 +156,7 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n b := make([]byte, 1500) n, _, err := c.ReadFrom(b) if err != nil { - if isTimeout(err) { + if errors.Is(err, os.ErrDeadlineExceeded) { log.Debug("dhcpv4: didn't receive dhcp response") return false, false, nil @@ -176,20 +176,21 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n log.Debug("dhcpv4: received message from server: %s", response.Summary()) - if !(response.OpCode == dhcpv4.OpcodeBootReply && - response.HWType == iana.HWTypeEthernet && - bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) && - bytes.Equal(response.TransactionID[:], req.TransactionID[:]) && - response.Options.Has(dhcpv4.OptionDHCPMessageType)) { - - log.Debug("dhcpv4: received message from server doesn't match our request") + switch { + case + response.OpCode != dhcpv4.OpcodeBootReply, + response.HWType != iana.HWTypeEthernet, + !bytes.Equal(response.ClientHWAddr, iface.HardwareAddr), + response.TransactionID != req.TransactionID, + !response.Options.Has(dhcpv4.OptionDHCPMessageType): + log.Debug("dhcpv4: received response doesn't match the request") return false, true, nil + default: + log.Tracef("dhcpv4: the packet is from an active dhcp server") + + return true, false, nil } - - log.Tracef("dhcpv4: the packet is from an active dhcp server") - - return true, false, nil } // checkOtherDHCPv6 sends a DHCP request to the specified network interface, and @@ -275,7 +276,7 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) n, _, err := c.ReadFrom(b) if err != nil { - if isTimeout(err) { + if errors.Is(err, os.ErrDeadlineExceeded) { log.Debug("dhcpv6: didn't receive dhcp response") return false, false, nil @@ -318,15 +319,3 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) return true, false, nil } - -// isTimeout returns true if err is an operation timeout error from net package. -// -// TODO(e.burkov): Consider moving into netutil. -func isTimeout(err error) (ok bool) { - var operr *net.OpError - if errors.As(err, &operr) { - return operr.Timeout() - } - - return false -} diff --git a/internal/aghnet/hostgen.go b/internal/aghnet/hostgen.go index d9278515..683c8d9f 100644 --- a/internal/aghnet/hostgen.go +++ b/internal/aghnet/hostgen.go @@ -11,7 +11,7 @@ const ( ipv6HostnameMaxLen = len("ff80-f076-0000-0000-0000-0000-0000-0010") ) -// generateIPv4Hostname generates the hostname for specific IP version. +// generateIPv4Hostname generates the hostname by IP address version 4. func generateIPv4Hostname(ipv4 net.IP) (hostname string) { hnData := make([]byte, 0, ipv4HostnameMaxLen) for i, part := range ipv4 { @@ -24,7 +24,7 @@ func generateIPv4Hostname(ipv4 net.IP) (hostname string) { return string(hnData) } -// generateIPv6Hostname generates the hostname for specific IP version. +// generateIPv6Hostname generates the hostname by IP address version 6. func generateIPv6Hostname(ipv6 net.IP) (hostname string) { hnData := make([]byte, 0, ipv6HostnameMaxLen) for i, partsNum := 0, net.IPv6len/2; i < partsNum; i++ { @@ -51,12 +51,11 @@ func generateIPv6Hostname(ipv6 net.IP) (hostname string) { // // ff80-f076-0000-0000-0000-0000-0000-0010 // +// ip must be either an IPv4 or an IPv6. func GenerateHostname(ip net.IP) (hostname string) { if ipv4 := ip.To4(); ipv4 != nil { return generateIPv4Hostname(ipv4) - } else if ipv6 := ip.To16(); ipv6 != nil { - return generateIPv6Hostname(ipv6) } - return "" + return generateIPv6Hostname(ip) } diff --git a/internal/aghnet/hostgen_test.go b/internal/aghnet/hostgen_test.go index 37121628..d37e556b 100644 --- a/internal/aghnet/hostgen_test.go +++ b/internal/aghnet/hostgen_test.go @@ -8,41 +8,57 @@ import ( ) func TestGenerateHostName(t *testing.T) { - testCases := []struct { - name string - want string - ip net.IP - }{{ - name: "good_ipv4", - want: "127-0-0-1", - ip: net.IP{127, 0, 0, 1}, - }, { - name: "bad_ipv4", - want: "", - ip: net.IP{127, 0, 0, 1, 0}, - }, { - name: "good_ipv6", - want: "fe00-0000-0000-0000-0000-0000-0000-0001", - ip: net.ParseIP("fe00::1"), - }, { - name: "bad_ipv6", - want: "", - ip: net.IP{ - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - }, - }, { - name: "nil", - want: "", - ip: nil, - }} + t.Run("valid", func(t *testing.T) { + testCases := []struct { + name string + want string + ip net.IP + }{{ + name: "good_ipv4", + want: "127-0-0-1", + ip: net.IP{127, 0, 0, 1}, + }, { + name: "good_ipv6", + want: "fe00-0000-0000-0000-0000-0000-0000-0001", + ip: net.ParseIP("fe00::1"), + }, { + name: "4to6", + want: "1-2-3-4", + ip: net.ParseIP("::ffff:1.2.3.4"), + }} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - hostname := GenerateHostname(tc.ip) - assert.Equal(t, tc.want, hostname) - }) - } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + hostname := GenerateHostname(tc.ip) + assert.Equal(t, tc.want, hostname) + }) + } + }) + + t.Run("invalid", func(t *testing.T) { + testCases := []struct { + name string + ip net.IP + }{{ + name: "bad_ipv4", + ip: net.IP{127, 0, 0, 1, 0}, + }, { + name: "bad_ipv6", + ip: net.IP{ + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + }, + }, { + name: "nil", + ip: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Panics(t, func() { GenerateHostname(tc.ip) }) + }) + } + }) } diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go index 198d4c78..ee345905 100644 --- a/internal/aghnet/hostscontainer.go +++ b/internal/aghnet/hostscontainer.go @@ -51,7 +51,7 @@ type requestMatcher struct { // // It's safe for concurrent use. func (rm *requestMatcher) MatchRequest( - req urlfilter.DNSRequest, + req *urlfilter.DNSRequest, ) (res *urlfilter.DNSResult, ok bool) { switch req.DNSType { case dns.TypeA, dns.TypeAAAA, dns.TypePTR: @@ -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,41 +335,68 @@ 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) } } -// writeRules writes the actual rule for the qtype and the PTR for the -// host-ip pair into internal builders. +// 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) { arpa, err := netutil.IPToReversedAddr(ip) if err != nil { @@ -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 a141ce00..1f75a3c9 100644 --- a/internal/aghnet/hostscontainer_test.go +++ b/internal/aghnet/hostscontainer_test.go @@ -3,7 +3,6 @@ package aghnet import ( "io/fs" "net" - "os" "path" "strings" "sync/atomic" @@ -13,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" @@ -160,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) { @@ -194,7 +210,7 @@ func TestHostsContainer_refresh(t *testing.T) { // Require the changes are written. require.Eventually(t, func() bool { - res, ok := hc.MatchRequest(urlfilter.DNSRequest{ + res, ok := hc.MatchRequest(&urlfilter.DNSRequest{ Hostname: "hostname", DNSType: dns.TypeA, }) @@ -208,7 +224,7 @@ func TestHostsContainer_refresh(t *testing.T) { // Require the changes are written. require.Eventually(t, func() bool { - res, ok := hc.MatchRequest(urlfilter.DNSRequest{ + res, ok := hc.MatchRequest(&urlfilter.DNSRequest{ Hostname: "hostname", DNSType: dns.TypeA, }) @@ -281,13 +297,14 @@ func TestHostsContainer_PathsToPatterns(t *testing.T) { } func TestHostsContainer_Translate(t *testing.T) { - testdata := os.DirFS("./testdata") stubWatcher := aghtest.FSWatcher{ OnEvents: func() (e <-chan struct{}) { return nil }, OnAdd: func(name string) (err error) { return nil }, OnClose: func() (err error) { panic("not implemented") }, } + require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) + hc, err := NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts") require.NoError(t, err) testutil.CleanupAndRequireSuccess(t, hc.Close) @@ -360,13 +377,18 @@ func TestHostsContainer_Translate(t *testing.T) { func TestHostsContainer(t *testing.T) { const listID = 1234 - testdata := os.DirFS("./testdata") + require.NoError(t, fstest.TestFS(testdata, "etc_hosts")) testCases := []struct { - want []*rules.DNSRewrite + req *urlfilter.DNSRequest name string - req urlfilter.DNSRequest + 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{ + 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{ + }, { + 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{ + req: &urlfilter.DNSRequest{ Hostname: "say.hello", DNSType: dns.TypeA, }, - }, { + name: "hello_subdomain", want: []*rules.DNSRewrite{}, - name: "hello_alias_subdomain", - req: urlfilter.DNSRequest{ + }, { + 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", + req: &urlfilter.DNSRequest{ + Hostname: "nonexistent.example", DNSType: dns.TypeA, }, + name: "non-existing", + want: []*rules.DNSRewrite{}, }, { - want: nil, - name: "bad_type", - req: urlfilter.DNSRequest{ + 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", + }, { + req: &urlfilter.DNSRequest{ + 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 ecb70fa8..2de9c630 100644 --- a/internal/aghnet/net.go +++ b/internal/aghnet/net.go @@ -2,19 +2,31 @@ package aghnet import ( + "bytes" "encoding/json" "fmt" "io" "net" - "os/exec" - "strings" "syscall" + "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" ) +// Variables and functions to substitute in tests. +var ( + // aghosRunCommand is the function to run shell commands. + aghosRunCommand = aghos.RunCommand + + // netInterfaces is the function to get the available network interfaces. + netInterfaceAddrs = net.InterfaceAddrs + + // rootDirFS is the filesystem pointing to the root directory. + rootDirFS = aghos.RootDirFS() +) + // ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about // the IP being static is available. const ErrNoStaticIPInfo errors.Error = "no information about static ip" @@ -32,39 +44,29 @@ func IfaceSetStaticIP(ifaceName string) (err error) { } // GatewayIP returns IP address of interface's gateway. -func GatewayIP(ifaceName string) net.IP { - cmd := exec.Command("ip", "route", "show", "dev", ifaceName) - log.Tracef("executing %s %v", cmd.Path, cmd.Args) - d, err := cmd.Output() - if err != nil || cmd.ProcessState.ExitCode() != 0 { +// +// TODO(e.burkov): Investigate if the gateway address may be fetched in another +// way since not every machine has the software installed. +func GatewayIP(ifaceName string) (ip net.IP) { + code, out, err := aghosRunCommand("ip", "route", "show", "dev", ifaceName) + if err != nil { + log.Debug("%s", err) + + return nil + } else if code != 0 { + log.Debug("fetching gateway ip: unexpected exit code: %d", code) + return nil } - fields := strings.Fields(string(d)) + fields := bytes.Fields(out) // The meaningful "ip route" command output should contain the word // "default" at first field and default gateway IP address at third field. - if len(fields) < 3 || fields[0] != "default" { + if len(fields) < 3 || string(fields[0]) != "default" { return nil } - return net.ParseIP(fields[2]) -} - -// CanBindPort checks if we can bind to the given port. -func CanBindPort(port int) (can bool, err error) { - var addr *net.TCPAddr - addr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port)) - if err != nil { - return false, err - } - - var listener *net.TCPListener - listener, err = net.ListenTCP("tcp", addr) - if err != nil { - return false, err - } - _ = listener.Close() - return true, nil + return net.ParseIP(string(fields[2])) } // CanBindPrivilegedPorts checks if current process can bind to privileged @@ -99,19 +101,19 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) { }) } -// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only -// we do not return link-local addresses here -func GetValidNetInterfacesForWeb() ([]*NetInterface, error) { +// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and +// WEB only we do not return link-local addresses here. +// +// TODO(e.burkov): Can't properly test the function since it's nontrivial to +// substitute net.Interface.Addrs and the net.InterfaceAddrs can't be used. +func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) { ifaces, err := net.Interfaces() if err != nil { return nil, fmt.Errorf("couldn't get interfaces: %w", err) - } - if len(ifaces) == 0 { + } else if len(ifaces) == 0 { return nil, errors.Error("couldn't find any legible interface") } - var netInterfaces []*NetInterface - for _, iface := range ifaces { var addrs []net.Addr addrs, err = iface.Addrs() @@ -131,27 +133,34 @@ func GetValidNetInterfacesForWeb() ([]*NetInterface, error) { ipNet, ok := addr.(*net.IPNet) if !ok { // Should be net.IPNet, this is weird. - return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr) + return nil, fmt.Errorf("got %s that is not net.IPNet, it is %T", addr, addr) } + // Ignore link-local. if ipNet.IP.IsLinkLocalUnicast() { continue } + netIface.Addresses = append(netIface.Addresses, ipNet.IP) netIface.Subnets = append(netIface.Subnets, ipNet) } // Discard interfaces with no addresses. if len(netIface.Addresses) != 0 { - netInterfaces = append(netInterfaces, netIface) + netIfaces = append(netIfaces, netIface) } } - return netInterfaces, nil + return netIfaces, nil } -// GetInterfaceByIP returns the name of interface containing provided ip. -func GetInterfaceByIP(ip net.IP) string { +// InterfaceByIP returns the name of the interface bound to ip. +// +// 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 "" @@ -170,6 +179,8 @@ 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 GetValidNetInterfacesForWeb. func GetSubnet(ifaceName string) *net.IPNet { netIfaces, err := GetValidNetInterfacesForWeb() if err != nil { @@ -220,29 +231,21 @@ func IsAddrInUse(err error) (ok bool) { // CollectAllIfacesAddrs returns the slice of all network interfaces IP // addresses without port number. func CollectAllIfacesAddrs() (addrs []string, err error) { - var ifaces []net.Interface - ifaces, err = net.Interfaces() + var ifaceAddrs []net.Addr + ifaceAddrs, err = netInterfaceAddrs() if err != nil { - return nil, fmt.Errorf("getting network interfaces: %w", err) + return nil, fmt.Errorf("getting interfaces addresses: %w", err) } - for _, iface := range ifaces { - var ifaceAddrs []net.Addr - ifaceAddrs, err = iface.Addrs() + for _, addr := range ifaceAddrs { + cidr := addr.String() + var ip net.IP + ip, _, err = net.ParseCIDR(cidr) if err != nil { - return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err) + return nil, fmt.Errorf("parsing cidr: %w", err) } - for _, addr := range ifaceAddrs { - cidr := addr.String() - var ip net.IP - ip, _, err = net.ParseCIDR(cidr) - if err != nil { - return nil, fmt.Errorf("parsing cidr: %w", err) - } - - addrs = append(addrs, ip.String()) - } + addrs = append(addrs, ip.String()) } return addrs, nil diff --git a/internal/aghnet/net_bsd.go b/internal/aghnet/net_bsd.go new file mode 100644 index 00000000..bd705e92 --- /dev/null +++ b/internal/aghnet/net_bsd.go @@ -0,0 +1,10 @@ +//go:build darwin || freebsd || openbsd +// +build darwin freebsd openbsd + +package aghnet + +import "github.com/AdguardTeam/AdGuardHome/internal/aghos" + +func canBindPrivilegedPorts() (can bool, err error) { + return aghos.HaveAdminRights() +} diff --git a/internal/aghnet/net_darwin.go b/internal/aghnet/net_darwin.go index 3c504988..296a18b0 100644 --- a/internal/aghnet/net_darwin.go +++ b/internal/aghnet/net_darwin.go @@ -4,10 +4,11 @@ package aghnet import ( + "bufio" + "bytes" "fmt" - "os" + "io" "regexp" - "strings" "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/errors" @@ -23,11 +24,7 @@ type hardwarePortInfo struct { static bool } -func canBindPrivilegedPorts() (can bool, err error) { - return aghos.HaveAdminRights() -} - -func ifaceHasStaticIP(ifaceName string) (bool, error) { +func ifaceHasStaticIP(ifaceName string) (ok bool, err error) { portInfo, err := getCurrentHardwarePortInfo(ifaceName) if err != nil { return false, err @@ -36,9 +33,10 @@ func ifaceHasStaticIP(ifaceName string) (bool, error) { return portInfo.static, nil } -// getCurrentHardwarePortInfo gets information for the specified network interface. +// getCurrentHardwarePortInfo gets information for the specified network +// interface. func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) { - // First of all we should find hardware port name + // First of all we should find hardware port name. m := getNetworkSetupHardwareReports() hardwarePort, ok := m[ifaceName] if !ok { @@ -48,6 +46,10 @@ func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) { return getHardwarePortInfo(hardwarePort) } +// hardwareReportsReg is the regular expression matching the lines of +// networksetup command output lines containing the interface information. +var hardwareReportsReg = regexp.MustCompile("Hardware Port: (.*?)\nDevice: (.*?)\n") + // getNetworkSetupHardwareReports parses the output of the `networksetup // -listallhardwareports` command it returns a map where the key is the // interface name, and the value is the "hardware port" returns nil if it fails @@ -56,54 +58,44 @@ func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) { // TODO(e.burkov): There should be more proper approach than parsing the // command output. For example, see // https://developer.apple.com/documentation/systemconfiguration. -func getNetworkSetupHardwareReports() map[string]string { - _, out, err := aghos.RunCommand("networksetup", "-listallhardwareports") +func getNetworkSetupHardwareReports() (reports map[string]string) { + _, out, err := aghosRunCommand("networksetup", "-listallhardwareports") if err != nil { return nil } - re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n") - if err != nil { - return nil + reports = make(map[string]string) + + matches := hardwareReportsReg.FindAllSubmatch(out, -1) + for _, m := range matches { + reports[string(m[2])] = string(m[1]) } - m := make(map[string]string) - - matches := re.FindAllStringSubmatch(out, -1) - for i := range matches { - port := matches[i][1] - device := matches[i][2] - m[device] = port - } - - return m + return reports } -func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) { - h := hardwarePortInfo{} +// hardwarePortReg is the regular expression matching the lines of networksetup +// command output lines containing the port information. +var hardwarePortReg = regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n") - _, out, err := aghos.RunCommand("networksetup", "-getinfo", hardwarePort) +func getHardwarePortInfo(hardwarePort string) (h hardwarePortInfo, err error) { + _, out, err := aghosRunCommand("networksetup", "-getinfo", hardwarePort) if err != nil { return h, err } - re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n") - - match := re.FindStringSubmatch(out) - if len(match) == 0 { + match := hardwarePortReg.FindSubmatch(out) + if len(match) != 4 { return h, errors.Error("could not find hardware port info") } - h.name = hardwarePort - h.ip = match[1] - h.subnet = match[2] - h.gatewayIP = match[3] - - if strings.Index(out, "Manual Configuration") == 0 { - h.static = true - } - - return h, nil + return hardwarePortInfo{ + name: hardwarePort, + ip: string(match[1]), + subnet: string(match[2]), + gatewayIP: string(match[3]), + static: bytes.Index(out, []byte("Manual Configuration")) == 0, + }, nil } func ifaceSetStaticIP(ifaceName string) (err error) { @@ -113,7 +105,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) { } if portInfo.static { - return errors.Error("IP address is already static") + return errors.Error("ip address is already static") } dnsAddrs, err := getEtcResolvConfServers() @@ -121,50 +113,62 @@ func ifaceSetStaticIP(ifaceName string) (err error) { return err } - args := make([]string, 0) - args = append(args, "-setdnsservers", portInfo.name) - args = append(args, dnsAddrs...) + args := append([]string{"-setdnsservers", portInfo.name}, dnsAddrs...) // Setting DNS servers is necessary when configuring a static IP - code, _, err := aghos.RunCommand("networksetup", args...) + code, _, err := aghosRunCommand("networksetup", args...) if err != nil { return err - } - if code != 0 { + } else if code != 0 { return fmt.Errorf("failed to set DNS servers, code=%d", code) } // Actually configures hardware port to have static IP - code, _, err = aghos.RunCommand("networksetup", "-setmanual", - portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP) + code, _, err = aghosRunCommand( + "networksetup", + "-setmanual", + portInfo.name, + portInfo.ip, + portInfo.subnet, + portInfo.gatewayIP, + ) if err != nil { return err - } - if code != 0 { + } else if code != 0 { return fmt.Errorf("failed to set DNS servers, code=%d", code) } return nil } +// etcResolvConfReg is the regular expression matching the lines of resolv.conf +// file containing a name server information. +var etcResolvConfReg = regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)") + // getEtcResolvConfServers returns a list of nameservers configured in // /etc/resolv.conf. -func getEtcResolvConfServers() ([]string, error) { - body, err := os.ReadFile("/etc/resolv.conf") +func getEtcResolvConfServers() (addrs []string, err error) { + const filename = "etc/resolv.conf" + + _, err = aghos.FileWalker(func(r io.Reader) (_ []string, _ bool, err error) { + sc := bufio.NewScanner(r) + for sc.Scan() { + matches := etcResolvConfReg.FindAllStringSubmatch(sc.Text(), -1) + if len(matches) == 0 { + continue + } + + for _, m := range matches { + addrs = append(addrs, m[1]) + } + } + + return nil, false, sc.Err() + }).Walk(rootDirFS, filename) if err != nil { - return nil, err - } - - re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)") - - matches := re.FindAllStringSubmatch(string(body), -1) - if len(matches) == 0 { - return nil, errors.Error("found no DNS servers in /etc/resolv.conf") - } - - addrs := make([]string, 0) - for i := range matches { - addrs = append(addrs, matches[i][1]) + return nil, fmt.Errorf("parsing etc/resolv.conf file: %w", err) + } else if len(addrs) == 0 { + return nil, fmt.Errorf("found no dns servers in %s", filename) } return addrs, nil diff --git a/internal/aghnet/net_darwin_test.go b/internal/aghnet/net_darwin_test.go new file mode 100644 index 00000000..905600d5 --- /dev/null +++ b/internal/aghnet/net_darwin_test.go @@ -0,0 +1,261 @@ +package aghnet + +import ( + "io/fs" + "testing" + "testing/fstest" + + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" +) + +func TestIfaceHasStaticIP(t *testing.T) { + testCases := []struct { + name string + shell mapShell + ifaceName string + wantHas assert.BoolAssertionFunc + wantErrMsg string + }{{ + name: "success", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + }, + ifaceName: "en0", + wantHas: assert.False, + wantErrMsg: ``, + }, { + name: "success_static", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "Manual Configuration\nIP address: 1.2.3.4\n" + + "Subnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + }, + ifaceName: "en0", + wantHas: assert.True, + wantErrMsg: ``, + }, { + name: "reports_error", + shell: theOnlyCmd( + "networksetup -listallhardwareports", + 0, + "", + errors.Error("can't list"), + ), + ifaceName: "en0", + wantHas: assert.False, + wantErrMsg: `could not find hardware port for en0`, + }, { + name: "port_error", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: errors.Error("can't get"), + out: ``, + code: 0, + }, + }, + ifaceName: "en0", + wantHas: assert.False, + wantErrMsg: `can't get`, + }, { + name: "port_bad_output", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "nothing meaningful", + code: 0, + }, + }, + ifaceName: "en0", + wantHas: assert.False, + wantErrMsg: `could not find hardware port info`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + substShell(t, tc.shell.RunCmd) + + has, err := IfaceHasStaticIP(tc.ifaceName) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + tc.wantHas(t, has) + }) + } +} + +func TestIfaceSetStaticIP(t *testing.T) { + succFsys := fstest.MapFS{ + "etc/resolv.conf": &fstest.MapFile{ + Data: []byte(`nameserver 1.1.1.1`), + }, + } + panicFsys := &aghtest.FS{ + OnOpen: func(name string) (fs.File, error) { panic("not implemented") }, + } + + testCases := []struct { + name string + shell mapShell + fsys fs.FS + wantErrMsg string + }{{ + name: "success", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + "networksetup -setdnsservers hwport 1.1.1.1": { + err: nil, + out: "", + code: 0, + }, + "networksetup -setmanual hwport 1.2.3.4 255.255.255.0 1.2.3.1": { + err: nil, + out: "", + code: 0, + }, + }, + fsys: succFsys, + wantErrMsg: ``, + }, { + name: "static_already", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "Manual Configuration\nIP address: 1.2.3.4\n" + + "Subnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + }, + fsys: panicFsys, + wantErrMsg: `ip address is already static`, + }, { + name: "reports_error", + shell: theOnlyCmd( + "networksetup -listallhardwareports", + 0, + "", + errors.Error("can't list"), + ), + fsys: panicFsys, + wantErrMsg: `could not find hardware port for en0`, + }, { + name: "resolv_conf_error", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + }, + fsys: fstest.MapFS{ + "etc/resolv.conf": &fstest.MapFile{ + Data: []byte("this resolv.conf is invalid"), + }, + }, + wantErrMsg: `found no dns servers in etc/resolv.conf`, + }, { + name: "set_dns_error", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + "networksetup -setdnsservers hwport 1.1.1.1": { + err: errors.Error("can't set"), + out: "", + code: 0, + }, + }, + fsys: succFsys, + wantErrMsg: `can't set`, + }, { + name: "set_manual_error", + shell: mapShell{ + "networksetup -listallhardwareports": { + err: nil, + out: "Hardware Port: hwport\nDevice: en0\n", + code: 0, + }, + "networksetup -getinfo hwport": { + err: nil, + out: "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n", + code: 0, + }, + "networksetup -setdnsservers hwport 1.1.1.1": { + err: nil, + out: "", + code: 0, + }, + "networksetup -setmanual hwport 1.2.3.4 255.255.255.0 1.2.3.1": { + err: errors.Error("can't set"), + out: "", + code: 0, + }, + }, + fsys: succFsys, + wantErrMsg: `can't set`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + substShell(t, tc.shell.RunCmd) + substRootDirFS(t, tc.fsys) + + err := IfaceSetStaticIP("en0") + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + }) + } +} diff --git a/internal/aghnet/net_freebsd.go b/internal/aghnet/net_freebsd.go index a5200fb8..85d40184 100644 --- a/internal/aghnet/net_freebsd.go +++ b/internal/aghnet/net_freebsd.go @@ -13,16 +13,12 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghos" ) -func canBindPrivilegedPorts() (can bool, err error) { - return aghos.HaveAdminRights() -} - func ifaceHasStaticIP(ifaceName string) (ok bool, err error) { const rcConfFilename = "etc/rc.conf" walker := aghos.FileWalker(interfaceName(ifaceName).rcConfStaticConfig) - return walker.Walk(aghos.RootDirFS(), rcConfFilename) + return walker.Walk(rootDirFS, rcConfFilename) } // rcConfStaticConfig checks if the interface is configured by /etc/rc.conf to diff --git a/internal/aghnet/net_freebsd_test.go b/internal/aghnet/net_freebsd_test.go index 3781b154..2c758360 100644 --- a/internal/aghnet/net_freebsd_test.go +++ b/internal/aghnet/net_freebsd_test.go @@ -4,56 +4,74 @@ package aghnet import ( - "strings" + "io/fs" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestRcConfStaticConfig(t *testing.T) { - const iface interfaceName = `em0` - const nl = "\n" +func TestIfaceHasStaticIP(t *testing.T) { + const ( + ifaceName = `em0` + rcConf = "etc/rc.conf" + ) testCases := []struct { - name string - rcconfData string - wantCont bool + name string + rootFsys fs.FS + wantHas assert.BoolAssertionFunc }{{ - name: "simple", - rcconfData: `ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl, - wantCont: false, + name: "simple", + rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{ + Data: []byte(`ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl), + }}, + wantHas: assert.True, }, { - name: "case_insensitiveness", - rcconfData: `ifconfig_em0="InEt 127.0.0.253 NeTmAsK 0xffffffff"` + nl, - wantCont: false, + name: "case_insensitiveness", + rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{ + Data: []byte(`ifconfig_` + ifaceName + `="InEt 127.0.0.253 NeTmAsK 0xffffffff"` + nl), + }}, + wantHas: assert.True, }, { name: "comments_and_trash", - rcconfData: `# comment 1` + nl + - `` + nl + - `# comment 2` + nl + - `ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl, - wantCont: false, + rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{ + Data: []byte(`# comment 1` + nl + + `` + nl + + `# comment 2` + nl + + `ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl, + ), + }}, + wantHas: assert.True, }, { name: "aliases", - rcconfData: `ifconfig_em0_alias="inet 127.0.0.1/24"` + nl + - `ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl, - wantCont: false, + rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{ + Data: []byte(`ifconfig_` + ifaceName + `_alias="inet 127.0.0.1/24"` + nl + + `ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl, + ), + }}, + wantHas: assert.True, }, { name: "incorrect_config", - rcconfData: `ifconfig_em0="inet6 127.0.0.253 netmask 0xffffffff"` + nl + - `ifconfig_em0="inet 256.256.256.256 netmask 0xffffffff"` + nl + - `ifconfig_em0=""` + nl, - wantCont: true, + rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{ + Data: []byte( + `ifconfig_` + ifaceName + `="inet6 127.0.0.253 netmask 0xffffffff"` + nl + + `ifconfig_` + ifaceName + `="inet 256.256.256.256 netmask 0xffffffff"` + nl + + `ifconfig_` + ifaceName + `=""` + nl, + ), + }}, + wantHas: assert.False, }} for _, tc := range testCases { - r := strings.NewReader(tc.rcconfData) t.Run(tc.name, func(t *testing.T) { - _, cont, err := iface.rcConfStaticConfig(r) + substRootDirFS(t, tc.rootFsys) + + has, err := IfaceHasStaticIP(ifaceName) require.NoError(t, err) - assert.Equal(t, tc.wantCont, cont) + tc.wantHas(t, has) }) } } diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go index 93414165..4be8835c 100644 --- a/internal/aghnet/net_linux.go +++ b/internal/aghnet/net_linux.go @@ -13,16 +13,44 @@ 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" ) +// dhcpсdConf is the name of /etc/dhcpcd.conf file in the root filesystem. +const dhcpcdConf = "etc/dhcpcd.conf" + +func canBindPrivilegedPorts() (can bool, err error) { + 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 res == 1 || adm, nil +} + // dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to // have a static IP. func (n interfaceName) dhcpcdStaticConfig(r io.Reader) (subsources []string, cont bool, err error) { s := bufio.NewScanner(r) - ifaceFound := findIfaceLine(s, string(n)) - if !ifaceFound { + if !findIfaceLine(s, string(n)) { return nil, true, s.Err() } @@ -61,9 +89,9 @@ func (n interfaceName) ifacesStaticConfig(r io.Reader) (sub []string, cont bool, fields := strings.Fields(line) fieldsNum := len(fields) - // Man page interfaces(5) declares that interface definition - // should consist of the key word "iface" followed by interface - // name, and method at fourth field. + // Man page interfaces(5) declares that interface definition should + // consist of the key word "iface" followed by interface name, and + // method at fourth field. if fieldsNum >= 4 && fields[0] == "iface" && fields[1] == string(n) && fields[3] == "static" { return nil, false, nil @@ -78,10 +106,10 @@ func (n interfaceName) ifacesStaticConfig(r io.Reader) (sub []string, cont bool, } func ifaceHasStaticIP(ifaceName string) (has bool, err error) { - // TODO(a.garipov): Currently, this function returns the first - // definitive result. So if /etc/dhcpcd.conf has a static IP while - // /etc/network/interfaces doesn't, it will return true. Perhaps this - // is not the most desirable behavior. + // TODO(a.garipov): Currently, this function returns the first definitive + // result. So if /etc/dhcpcd.conf has and /etc/network/interfaces has no + // static IP configuration, it will return true. Perhaps this is not the + // most desirable behavior. iface := interfaceName(ifaceName) @@ -90,17 +118,15 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) { filename string }{{ FileWalker: iface.dhcpcdStaticConfig, - filename: "etc/dhcpcd.conf", + filename: dhcpcdConf, }, { FileWalker: iface.ifacesStaticConfig, filename: "etc/network/interfaces", }} { - has, err = pair.Walk(aghos.RootDirFS(), pair.filename) + has, err = pair.Walk(rootDirFS, pair.filename) if err != nil { return false, err - } - - if has { + } else if has { return true, nil } } @@ -108,14 +134,6 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) { return false, ErrNoStaticIPInfo } -func canBindPrivilegedPorts() (can bool, err error) { - cnbs, err := unix.PrctlRetInt(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0) - // Don't check the error because it's always nil on Linux. - adm, _ := aghos.HaveAdminRights() - - return cnbs == 1 || adm, err -} - // findIfaceLine scans s until it finds the line that declares an interface with // the given name. If findIfaceLine can't find the line, it returns false. func findIfaceLine(s *bufio.Scanner, name string) (ok bool) { @@ -131,23 +149,23 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) { } // ifaceSetStaticIP configures the system to retain its current IP on the -// interface through dhcpdc.conf. +// interface through dhcpcd.conf. func ifaceSetStaticIP(ifaceName string) (err error) { ipNet := GetSubnet(ifaceName) if ipNet.IP == nil { return errors.Error("can't get IP address") } - gatewayIP := GatewayIP(ifaceName) - add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP, ipNet.IP) - - body, err := os.ReadFile("/etc/dhcpcd.conf") + body, err := os.ReadFile(dhcpcdConf) if err != nil && !errors.Is(err, os.ErrNotExist) { return err } + gatewayIP := GatewayIP(ifaceName) + add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP) + body = append(body, []byte(add)...) - err = maybe.WriteFile("/etc/dhcpcd.conf", body, 0o644) + err = maybe.WriteFile(dhcpcdConf, body, 0o644) if err != nil { return fmt.Errorf("writing conf: %w", err) } @@ -157,22 +175,24 @@ func ifaceSetStaticIP(ifaceName string) (err error) { // dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that // configure the interface to have a static IP. -func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gatewayIP, dnsIP net.IP) (conf string) { - var body []byte - - add := fmt.Sprintf( - "\n# %[1]s added by AdGuard Home.\ninterface %[1]s\nstatic ip_address=%s\n", +func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) { + b := &strings.Builder{} + stringutil.WriteToBuilder( + b, + "\n# ", ifaceName, - ipNet) - body = append(body, []byte(add)...) + " added by AdGuard Home.\ninterface ", + ifaceName, + "\nstatic ip_address=", + ipNet.String(), + "\n", + ) - if gatewayIP != nil { - add = fmt.Sprintf("static routers=%s\n", gatewayIP) - body = append(body, []byte(add)...) + if gwIP != nil { + stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n") } - add = fmt.Sprintf("static domain_name_servers=%s\n\n", dnsIP) - body = append(body, []byte(add)...) + stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n") - return string(body) + return b.String() } diff --git a/internal/aghnet/net_linux_test.go b/internal/aghnet/net_linux_test.go index bf2cecfe..838802ff 100644 --- a/internal/aghnet/net_linux_test.go +++ b/internal/aghnet/net_linux_test.go @@ -4,152 +4,124 @@ package aghnet import ( - "bytes" - "net" + "io/fs" "testing" + "testing/fstest" + "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestDHCPCDStaticConfig(t *testing.T) { - const iface interfaceName = `wlan0` +func TestHasStaticIP(t *testing.T) { + const ifaceName = "wlan0" + + const ( + dhcpcd = "etc/dhcpcd.conf" + netifaces = "etc/network/interfaces" + ) testCases := []struct { - name string - data []byte - wantCont bool - }{{ - name: "has_not", - data: []byte(`#comment` + nl + - `# comment` + nl + - `interface eth0` + nl + - `static ip_address=192.168.0.1/24` + nl + - `# interface ` + iface + nl + - `static ip_address=192.168.1.1/24` + nl + - `# comment` + nl, - ), - wantCont: true, - }, { - name: "has", - data: []byte(`#comment` + nl + - `# comment` + nl + - `interface eth0` + nl + - `static ip_address=192.168.0.1/24` + nl + - `# interface ` + iface + nl + - `static ip_address=192.168.1.1/24` + nl + - `# comment` + nl + - `interface ` + iface + nl + - `# comment` + nl + - `static ip_address=192.168.2.1/24` + nl, - ), - wantCont: false, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - r := bytes.NewReader(tc.data) - _, cont, err := iface.dhcpcdStaticConfig(r) - require.NoError(t, err) - - assert.Equal(t, tc.wantCont, cont) - }) - } -} - -func TestIfacesStaticConfig(t *testing.T) { - const iface interfaceName = `enp0s3` - - testCases := []struct { - name string - data []byte - wantCont bool - wantPatterns []string - }{{ - name: "has_not", - data: []byte(`allow-hotplug ` + iface + nl + - `#iface enp0s3 inet static` + nl + - `# address 192.168.0.200` + nl + - `# netmask 255.255.255.0` + nl + - `# gateway 192.168.0.1` + nl + - `iface ` + iface + ` inet dhcp` + nl, - ), - wantCont: true, - wantPatterns: []string{}, - }, { - name: "has", - data: []byte(`allow-hotplug ` + iface + nl + - `iface ` + iface + ` inet static` + nl + - ` address 192.168.0.200` + nl + - ` netmask 255.255.255.0` + nl + - ` gateway 192.168.0.1` + nl + - `#iface ` + iface + ` inet dhcp` + nl, - ), - wantCont: false, - wantPatterns: []string{}, - }, { - name: "return_patterns", - data: []byte(`source hello` + nl + - `source world` + nl + - `#iface ` + iface + ` inet static` + nl, - ), - wantCont: true, - wantPatterns: []string{"hello", "world"}, - }, { - // This one tests if the first found valid interface prevents - // checking files under the `source` directive. - name: "ignore_patterns", - data: []byte(`source hello` + nl + - `source world` + nl + - `iface ` + iface + ` inet static` + nl, - ), - wantCont: false, - wantPatterns: []string{}, - }} - - for _, tc := range testCases { - r := bytes.NewReader(tc.data) - t.Run(tc.name, func(t *testing.T) { - patterns, has, err := iface.ifacesStaticConfig(r) - require.NoError(t, err) - - assert.Equal(t, tc.wantCont, has) - assert.ElementsMatch(t, tc.wantPatterns, patterns) - }) - } -} - -func TestSetStaticIPdhcpcdConf(t *testing.T) { - testCases := []struct { + rootFsys fs.FS name string - dhcpcdConf string - routers net.IP + wantHas assert.BoolAssertionFunc + wantErrMsg string }{{ - name: "with_gateway", - dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl + - `interface wlan0` + nl + - `static ip_address=192.168.0.2/24` + nl + - `static routers=192.168.0.1` + nl + - `static domain_name_servers=192.168.0.2` + nl + nl, - routers: net.IP{192, 168, 0, 1}, + rootFsys: fstest.MapFS{ + dhcpcd: &fstest.MapFile{ + Data: []byte(`#comment` + nl + + `# comment` + nl + + `interface eth0` + nl + + `static ip_address=192.168.0.1/24` + nl + + `# interface ` + ifaceName + nl + + `static ip_address=192.168.1.1/24` + nl + + `# comment` + nl, + ), + }, + }, + name: "dhcpcd_has_not", + wantHas: assert.False, + wantErrMsg: `no information about static ip`, }, { - name: "without_gateway", - dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl + - `interface wlan0` + nl + - `static ip_address=192.168.0.2/24` + nl + - `static domain_name_servers=192.168.0.2` + nl + nl, - routers: nil, + rootFsys: fstest.MapFS{ + dhcpcd: &fstest.MapFile{ + Data: []byte(`#comment` + nl + + `# comment` + nl + + `interface ` + ifaceName + nl + + `static ip_address=192.168.0.1/24` + nl + + `# interface ` + ifaceName + nl + + `static ip_address=192.168.1.1/24` + nl + + `# comment` + nl, + ), + }, + }, + name: "dhcpcd_has", + wantHas: assert.True, + wantErrMsg: ``, + }, { + rootFsys: fstest.MapFS{ + netifaces: &fstest.MapFile{ + Data: []byte(`allow-hotplug ` + ifaceName + nl + + `#iface enp0s3 inet static` + nl + + `# address 192.168.0.200` + nl + + `# netmask 255.255.255.0` + nl + + `# gateway 192.168.0.1` + nl + + `iface ` + ifaceName + ` inet dhcp` + nl, + ), + }, + }, + name: "netifaces_has_not", + wantHas: assert.False, + wantErrMsg: `no information about static ip`, + }, { + rootFsys: fstest.MapFS{ + netifaces: &fstest.MapFile{ + Data: []byte(`allow-hotplug ` + ifaceName + nl + + `iface ` + ifaceName + ` inet static` + nl + + ` address 192.168.0.200` + nl + + ` netmask 255.255.255.0` + nl + + ` gateway 192.168.0.1` + nl + + `#iface ` + ifaceName + ` inet dhcp` + nl, + ), + }, + }, + name: "netifaces_has", + wantHas: assert.True, + wantErrMsg: ``, + }, { + rootFsys: fstest.MapFS{ + netifaces: &fstest.MapFile{ + Data: []byte(`source hello` + nl + + `#iface ` + ifaceName + ` inet static` + nl, + ), + }, + "hello": &fstest.MapFile{ + Data: []byte(`iface ` + ifaceName + ` inet static` + nl), + }, + }, + name: "netifaces_another_file", + wantHas: assert.True, + wantErrMsg: ``, + }, { + rootFsys: fstest.MapFS{ + netifaces: &fstest.MapFile{ + Data: []byte(`source hello` + nl + + `iface ` + ifaceName + ` inet static` + nl, + ), + }, + }, + name: "netifaces_ignore_another", + wantHas: assert.True, + wantErrMsg: ``, }} - ipNet := &net.IPNet{ - IP: net.IP{192, 168, 0, 2}, - Mask: net.IPMask{255, 255, 255, 0}, - } - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - s := dhcpcdConfIface("wlan0", ipNet, tc.routers, net.IP{192, 168, 0, 2}) - assert.Equal(t, tc.dhcpcdConf, s) + substRootDirFS(t, tc.rootFsys) + + has, err := IfaceHasStaticIP(ifaceName) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + tc.wantHas(t, has) }) } } diff --git a/internal/aghnet/net_openbsd.go b/internal/aghnet/net_openbsd.go index 627db0ab..cf911105 100644 --- a/internal/aghnet/net_openbsd.go +++ b/internal/aghnet/net_openbsd.go @@ -13,14 +13,10 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghos" ) -func canBindPrivilegedPorts() (can bool, err error) { - return aghos.HaveAdminRights() -} - func ifaceHasStaticIP(ifaceName string) (ok bool, err error) { filename := fmt.Sprintf("etc/hostname.%s", ifaceName) - return aghos.FileWalker(hostnameIfStaticConfig).Walk(aghos.RootDirFS(), filename) + return aghos.FileWalker(hostnameIfStaticConfig).Walk(rootDirFS, filename) } // hostnameIfStaticConfig checks if the interface is configured by diff --git a/internal/aghnet/net_openbsd_test.go b/internal/aghnet/net_openbsd_test.go index e157d93a..356799b7 100644 --- a/internal/aghnet/net_openbsd_test.go +++ b/internal/aghnet/net_openbsd_test.go @@ -4,49 +4,69 @@ package aghnet import ( - "strings" + "fmt" + "io/fs" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestHostnameIfStaticConfig(t *testing.T) { - const nl = "\n" +func TestIfaceHasStaticIP(t *testing.T) { + const ifaceName = "em0" + + confFile := fmt.Sprintf("etc/hostname.%s", ifaceName) testCases := []struct { - name string - rcconfData string - wantHas bool + name string + rootFsys fs.FS + wantHas assert.BoolAssertionFunc }{{ - name: "simple", - rcconfData: `inet 127.0.0.253` + nl, - wantHas: true, + name: "simple", + rootFsys: fstest.MapFS{ + confFile: &fstest.MapFile{ + Data: []byte(`inet 127.0.0.253` + nl), + }, + }, + wantHas: assert.True, }, { - name: "case_sensitiveness", - rcconfData: `InEt 127.0.0.253` + nl, - wantHas: false, + name: "case_sensitiveness", + rootFsys: fstest.MapFS{ + confFile: &fstest.MapFile{ + Data: []byte(`InEt 127.0.0.253` + nl), + }, + }, + wantHas: assert.False, }, { name: "comments_and_trash", - rcconfData: `# comment 1` + nl + - `` + nl + - `# inet 127.0.0.253` + nl + - `inet` + nl, - wantHas: false, + rootFsys: fstest.MapFS{ + confFile: &fstest.MapFile{ + Data: []byte(`# comment 1` + nl + nl + + `# inet 127.0.0.253` + nl + + `inet` + nl, + ), + }, + }, + wantHas: assert.False, }, { name: "incorrect_config", - rcconfData: `inet6 127.0.0.253` + nl + - `inet 256.256.256.256` + nl, - wantHas: false, + rootFsys: fstest.MapFS{ + confFile: &fstest.MapFile{ + Data: []byte(`inet6 127.0.0.253` + nl + `inet 256.256.256.256` + nl), + }, + }, + wantHas: assert.False, }} for _, tc := range testCases { - r := strings.NewReader(tc.rcconfData) t.Run(tc.name, func(t *testing.T) { - _, has, err := hostnameIfStaticConfig(r) + substRootDirFS(t, tc.rootFsys) + + has, err := IfaceHasStaticIP(ifaceName) require.NoError(t, err) - assert.Equal(t, tc.wantHas, has) + tc.wantHas(t, has) }) } } diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go index b5bf2297..d4ee59ee 100644 --- a/internal/aghnet/net_test.go +++ b/internal/aghnet/net_test.go @@ -1,10 +1,17 @@ package aghnet import ( + "bytes" + "encoding/json" + "fmt" + "io/fs" "net" + "os" + "strings" "testing" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" @@ -15,7 +22,117 @@ func TestMain(m *testing.M) { aghtest.DiscardLogOutput(m) } -func TestGetInterfaceByIP(t *testing.T) { +// testdata is the filesystem containing data for testing the package. +var testdata fs.FS = os.DirFS("./testdata") + +// substRootDirFS replaces the aghos.RootDirFS function used throughout the +// package with fsys for tests ran under t. +func substRootDirFS(t testing.TB, fsys fs.FS) { + t.Helper() + + prev := rootDirFS + t.Cleanup(func() { rootDirFS = prev }) + rootDirFS = fsys +} + +// RunCmdFunc is the signature of aghos.RunCommand function. +type RunCmdFunc func(cmd string, args ...string) (code int, out []byte, err error) + +// substShell replaces the the aghos.RunCommand function used throughout the +// package with rc for tests ran under t. +func substShell(t testing.TB, rc RunCmdFunc) { + t.Helper() + + prev := aghosRunCommand + t.Cleanup(func() { aghosRunCommand = prev }) + aghosRunCommand = rc +} + +// mapShell is a substitution of aghos.RunCommand that maps the command to it's +// execution result. It's only needed to simplify testing. +// +// TODO(e.burkov): Perhaps put all the shell interactions behind an interface. +type mapShell map[string]struct { + err error + out string + code int +} + +// theOnlyCmd returns mapShell that only handles a single command and arguments +// combination from cmd. +func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) { + return mapShell{cmd: {code: code, out: out, err: err}} +} + +// RunCmd is a RunCmdFunc handled by s. +func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err error) { + key := strings.Join(append([]string{cmd}, args...), " ") + ret, ok := s[key] + if !ok { + return 0, nil, fmt.Errorf("unexpected shell command %q", key) + } + + return ret.code, []byte(ret.out), ret.err +} + +// ifaceAddrsFunc is the signature of net.InterfaceAddrs function. +type ifaceAddrsFunc func() (ifaces []net.Addr, err error) + +// substNetInterfaceAddrs replaces the the net.InterfaceAddrs function used +// throughout the package with f for tests ran under t. +func substNetInterfaceAddrs(t *testing.T, f ifaceAddrsFunc) { + t.Helper() + + prev := netInterfaceAddrs + t.Cleanup(func() { netInterfaceAddrs = prev }) + netInterfaceAddrs = f +} + +func TestGatewayIP(t *testing.T) { + const ifaceName = "ifaceName" + const cmd = "ip route show dev " + ifaceName + + testCases := []struct { + name string + shell mapShell + want net.IP + }{{ + name: "success_v4", + shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil), + want: net.IP{1, 2, 3, 4}.To16(), + }, { + name: "success_v6", + shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil), + want: net.IP{ + 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xFF, 0xFF, + }, + }, { + name: "bad_output", + shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil), + want: nil, + }, { + name: "err_runcmd", + shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")), + want: nil, + }, { + name: "bad_code", + shell: theOnlyCmd(cmd, 1, "", nil), + want: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + substShell(t, tc.shell.RunCmd) + + assert.Equal(t, tc.want, GatewayIP(ifaceName)) + }) + } +} + +func TestInterfaceByIP(t *testing.T) { ifaces, err := GetValidNetInterfacesForWeb() require.NoError(t, err) require.NotEmpty(t, ifaces) @@ -25,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) } }) @@ -125,3 +242,107 @@ func TestCheckPort(t *testing.T) { assert.NoError(t, err) }) } + +func TestCollectAllIfacesAddrs(t *testing.T) { + testCases := []struct { + name string + wantErrMsg string + addrs []net.Addr + wantAddrs []string + }{{ + name: "success", + wantErrMsg: ``, + addrs: []net.Addr{&net.IPNet{ + IP: net.IP{1, 2, 3, 4}, + Mask: net.CIDRMask(24, netutil.IPv4BitLen), + }, &net.IPNet{ + IP: net.IP{4, 3, 2, 1}, + Mask: net.CIDRMask(16, netutil.IPv4BitLen), + }}, + wantAddrs: []string{"1.2.3.4", "4.3.2.1"}, + }, { + name: "not_cidr", + wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`, + addrs: []net.Addr{&net.IPAddr{ + IP: net.IP{1, 2, 3, 4}, + }}, + wantAddrs: nil, + }, { + name: "empty", + wantErrMsg: ``, + addrs: []net.Addr{}, + wantAddrs: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return tc.addrs, nil }) + + addrs, err := CollectAllIfacesAddrs() + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + + assert.Equal(t, tc.wantAddrs, addrs) + }) + } + + t.Run("internal_error", func(t *testing.T) { + const errAddrs errors.Error = "can't get addresses" + const wantErrMsg string = `getting interfaces addresses: ` + string(errAddrs) + + substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return nil, errAddrs }) + + _, err := CollectAllIfacesAddrs() + testutil.AssertErrorMsg(t, wantErrMsg, err) + }) +} + +func TestIsAddrInUse(t *testing.T) { + t.Run("addr_in_use", func(t *testing.T) { + l, err := net.Listen("tcp", "0.0.0.0:0") + require.NoError(t, err) + testutil.CleanupAndRequireSuccess(t, l.Close) + + _, err = net.Listen(l.Addr().Network(), l.Addr().String()) + assert.True(t, IsAddrInUse(err)) + }) + + t.Run("another", func(t *testing.T) { + const anotherErr errors.Error = "not addr in use" + + assert.False(t, IsAddrInUse(anotherErr)) + }) +} + +func TestNetInterface_MarshalJSON(t *testing.T) { + const want = `{` + + `"hardware_address":"aa:bb:cc:dd:ee:ff",` + + `"flags":"up|multicast",` + + `"ip_addresses":["1.2.3.4","aaaa::1"],` + + `"name":"iface0",` + + `"mtu":1500` + + `}` + "\n" + + ip4, ip6 := net.IP{1, 2, 3, 4}, net.IP{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen) + + iface := &NetInterface{ + Addresses: []net.IP{ip4, ip6}, + Subnets: []*net.IPNet{{ + IP: ip4.Mask(mask4), + Mask: mask4, + }, { + IP: ip6.Mask(mask6), + Mask: mask6, + }}, + Name: "iface0", + HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, + Flags: net.FlagUp | net.FlagMulticast, + MTU: 1500, + } + + b := &bytes.Buffer{} + err := json.NewEncoder(b).Encode(iface) + require.NoError(t, err) + + assert.Equal(t, want, b.String()) +} diff --git a/internal/aghnet/net_windows.go b/internal/aghnet/net_windows.go index 128f5716..17499cce 100644 --- a/internal/aghnet/net_windows.go +++ b/internal/aghnet/net_windows.go @@ -1,5 +1,5 @@ -//go:build !(linux || darwin || freebsd || openbsd) -// +build !linux,!darwin,!freebsd,!openbsd +//go:build windows +// +build windows package aghnet @@ -14,7 +14,7 @@ import ( ) func canBindPrivilegedPorts() (can bool, err error) { - return aghos.HaveAdminRights() + return true, nil } func ifaceHasStaticIP(string) (ok bool, err error) { diff --git a/internal/aghnet/subnetdetector.go b/internal/aghnet/subnetdetector.go deleted file mode 100644 index fd338aaa..00000000 --- a/internal/aghnet/subnetdetector.go +++ /dev/null @@ -1,158 +0,0 @@ -package aghnet - -import ( - "net" -) - -// SubnetDetector describes IP address properties. -type SubnetDetector struct { - // spNets is the collection of special-purpose address registries as defined - // by RFC 6890. - spNets []*net.IPNet - - // locServedNets is the collection of locally-served networks as defined by - // RFC 6303. - locServedNets []*net.IPNet -} - -// NewSubnetDetector returns a new IP detector. -// -// TODO(a.garipov): Decide whether an error is actually needed. -func NewSubnetDetector() (snd *SubnetDetector, err error) { - spNets := []string{ - // "This" network. - "0.0.0.0/8", - // Private-Use Networks. - "10.0.0.0/8", - // Shared Address Space. - "100.64.0.0/10", - // Loopback. - "127.0.0.0/8", - // Link Local. - "169.254.0.0/16", - // Private-Use Networks. - "172.16.0.0/12", - // IETF Protocol Assignments. - "192.0.0.0/24", - // DS-Lite. - "192.0.0.0/29", - // TEST-NET-1 - "192.0.2.0/24", - // 6to4 Relay Anycast. - "192.88.99.0/24", - // Private-Use Networks. - "192.168.0.0/16", - // Network Interconnect Device Benchmark Testing. - "198.18.0.0/15", - // TEST-NET-2. - "198.51.100.0/24", - // TEST-NET-3. - "203.0.113.0/24", - // Reserved for Future Use. - "240.0.0.0/4", - // Limited Broadcast. - "255.255.255.255/32", - - // Loopback. - "::1/128", - // Unspecified. - "::/128", - // IPv4-IPv6 Translation Address. - "64:ff9b::/96", - - // IPv4-Mapped Address. Since this network is used for mapping - // IPv4 addresses, we don't include it. - // "::ffff:0:0/96", - - // Discard-Only Prefix. - "100::/64", - // IETF Protocol Assignments. - "2001::/23", - // TEREDO. - "2001::/32", - // Benchmarking. - "2001:2::/48", - // Documentation. - "2001:db8::/32", - // ORCHID. - "2001:10::/28", - // 6to4. - "2002::/16", - // Unique-Local. - "fc00::/7", - // Linked-Scoped Unicast. - "fe80::/10", - } - - // TODO(e.burkov): It's a subslice of the slice above. Should be done - // smarter. - locServedNets := []string{ - // IPv4. - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "127.0.0.0/8", - "169.254.0.0/16", - "192.0.2.0/24", - "198.51.100.0/24", - "203.0.113.0/24", - "255.255.255.255/32", - // IPv6. - "::/128", - "::1/128", - "fe80::/10", - "2001:db8::/32", - "fd00::/8", - } - - snd = &SubnetDetector{ - spNets: make([]*net.IPNet, len(spNets)), - locServedNets: make([]*net.IPNet, len(locServedNets)), - } - for i, ipnetStr := range spNets { - var ipnet *net.IPNet - _, ipnet, err = net.ParseCIDR(ipnetStr) - if err != nil { - return nil, err - } - - snd.spNets[i] = ipnet - } - for i, ipnetStr := range locServedNets { - var ipnet *net.IPNet - _, ipnet, err = net.ParseCIDR(ipnetStr) - if err != nil { - return nil, err - } - - snd.locServedNets[i] = ipnet - } - - return snd, nil -} - -// anyNetContains ranges through the given ipnets slice searching for the one -// which contains the ip. For internal use only. -// -// TODO(e.burkov): Think about memoization. -func anyNetContains(ipnets *[]*net.IPNet, ip net.IP) (is bool) { - for _, ipnet := range *ipnets { - if ipnet.Contains(ip) { - return true - } - } - - return false -} - -// IsSpecialNetwork returns true if IP address is contained by any of -// special-purpose IP address registries. It's safe for concurrent use. -func (snd *SubnetDetector) IsSpecialNetwork(ip net.IP) (is bool) { - return anyNetContains(&snd.spNets, ip) -} - -// IsLocallyServedNetwork returns true if IP address is contained by any of -// locally-served IP address registries. It's safe for concurrent use. -func (snd *SubnetDetector) IsLocallyServedNetwork(ip net.IP) (is bool) { - return anyNetContains(&snd.locServedNets, ip) -} diff --git a/internal/aghnet/subnetdetector_test.go b/internal/aghnet/subnetdetector_test.go deleted file mode 100644 index f4b7678c..00000000 --- a/internal/aghnet/subnetdetector_test.go +++ /dev/null @@ -1,252 +0,0 @@ -package aghnet - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSubnetDetector_DetectSpecialNetwork(t *testing.T) { - snd, err := NewSubnetDetector() - require.NoError(t, err) - - testCases := []struct { - name string - ip net.IP - want bool - }{{ - name: "not_specific", - ip: net.ParseIP("8.8.8.8"), - want: false, - }, { - name: "this_host_on_this_network", - ip: net.ParseIP("0.0.0.0"), - want: true, - }, { - name: "private-Use", - ip: net.ParseIP("10.0.0.0"), - want: true, - }, { - name: "shared_address_space", - ip: net.ParseIP("100.64.0.0"), - want: true, - }, { - name: "loopback", - ip: net.ParseIP("127.0.0.0"), - want: true, - }, { - name: "link_local", - ip: net.ParseIP("169.254.0.0"), - want: true, - }, { - name: "private-use", - ip: net.ParseIP("172.16.0.0"), - want: true, - }, { - name: "ietf_protocol_assignments", - ip: net.ParseIP("192.0.0.0"), - want: true, - }, { - name: "ds-lite", - ip: net.ParseIP("192.0.0.0"), - want: true, - }, { - name: "documentation_(test-net-1)", - ip: net.ParseIP("192.0.2.0"), - want: true, - }, { - name: "6to4_relay_anycast", - ip: net.ParseIP("192.88.99.0"), - want: true, - }, { - name: "private-use", - ip: net.ParseIP("192.168.0.0"), - want: true, - }, { - name: "benchmarking", - ip: net.ParseIP("198.18.0.0"), - want: true, - }, { - name: "documentation_(test-net-2)", - ip: net.ParseIP("198.51.100.0"), - want: true, - }, { - name: "documentation_(test-net-3)", - ip: net.ParseIP("203.0.113.0"), - want: true, - }, { - name: "reserved", - ip: net.ParseIP("240.0.0.0"), - want: true, - }, { - name: "limited_broadcast", - ip: net.ParseIP("255.255.255.255"), - want: true, - }, { - name: "loopback_address", - ip: net.ParseIP("::1"), - want: true, - }, { - name: "unspecified_address", - ip: net.ParseIP("::"), - want: true, - }, { - name: "ipv4-ipv6_translation", - ip: net.ParseIP("64:ff9b::"), - want: true, - }, { - name: "discard-only_address_block", - ip: net.ParseIP("100::"), - want: true, - }, { - name: "ietf_protocol_assignments", - ip: net.ParseIP("2001::"), - want: true, - }, { - name: "teredo", - ip: net.ParseIP("2001::"), - want: true, - }, { - name: "benchmarking", - ip: net.ParseIP("2001:2::"), - want: true, - }, { - name: "documentation", - ip: net.ParseIP("2001:db8::"), - want: true, - }, { - name: "orchid", - ip: net.ParseIP("2001:10::"), - want: true, - }, { - name: "6to4", - ip: net.ParseIP("2002::"), - want: true, - }, { - name: "unique-local", - ip: net.ParseIP("fc00::"), - want: true, - }, { - name: "linked-scoped_unicast", - ip: net.ParseIP("fe80::"), - want: true, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, snd.IsSpecialNetwork(tc.ip)) - }) - } -} - -func TestSubnetDetector_DetectLocallyServedNetwork(t *testing.T) { - snd, err := NewSubnetDetector() - require.NoError(t, err) - - testCases := []struct { - name string - ip net.IP - want bool - }{{ - name: "not_specific", - ip: net.ParseIP("8.8.8.8"), - want: false, - }, { - name: "private-Use", - ip: net.ParseIP("10.0.0.0"), - want: true, - }, { - name: "loopback", - ip: net.ParseIP("127.0.0.0"), - want: true, - }, { - name: "link_local", - ip: net.ParseIP("169.254.0.0"), - want: true, - }, { - name: "private-use", - ip: net.ParseIP("172.16.0.0"), - want: true, - }, { - name: "documentation_(test-net-1)", - ip: net.ParseIP("192.0.2.0"), - want: true, - }, { - name: "private-use", - ip: net.ParseIP("192.168.0.0"), - want: true, - }, { - name: "documentation_(test-net-2)", - ip: net.ParseIP("198.51.100.0"), - want: true, - }, { - name: "documentation_(test-net-3)", - ip: net.ParseIP("203.0.113.0"), - want: true, - }, { - name: "limited_broadcast", - ip: net.ParseIP("255.255.255.255"), - want: true, - }, { - name: "loopback_address", - ip: net.ParseIP("::1"), - want: true, - }, { - name: "unspecified_address", - ip: net.ParseIP("::"), - want: true, - }, { - name: "documentation", - ip: net.ParseIP("2001:db8::"), - want: true, - }, { - name: "linked-scoped_unicast", - ip: net.ParseIP("fe80::"), - want: true, - }, { - name: "locally_assigned", - ip: net.ParseIP("fd00::1"), - want: true, - }, { - name: "not_locally_assigned", - ip: net.ParseIP("fc00::1"), - want: false, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, snd.IsLocallyServedNetwork(tc.ip)) - }) - } -} - -func TestSubnetDetector_Detect_parallel(t *testing.T) { - t.Parallel() - - snd, err := NewSubnetDetector() - require.NoError(t, err) - - testFunc := func() { - for _, ip := range []net.IP{ - net.IPv4allrouter, - net.IPv4allsys, - net.IPv4bcast, - net.IPv4zero, - net.IPv6interfacelocalallnodes, - net.IPv6linklocalallnodes, - net.IPv6linklocalallrouters, - net.IPv6loopback, - net.IPv6unspecified, - } { - _ = snd.IsSpecialNetwork(ip) - _ = snd.IsLocallyServedNetwork(ip) - } - } - - const goroutinesNum = 50 - for i := 0; i < goroutinesNum; i++ { - go testFunc() - } -} diff --git a/internal/aghnet/systemresolvers.go b/internal/aghnet/systemresolvers.go index 777127a3..5ca8e9be 100644 --- a/internal/aghnet/systemresolvers.go +++ b/internal/aghnet/systemresolvers.go @@ -1,11 +1,5 @@ package aghnet -import ( - "time" - - "github.com/AdguardTeam/golibs/log" -) - // DefaultRefreshIvl is the default period of time between refreshing cached // addresses. // const DefaultRefreshIvl = 5 * time.Minute @@ -16,39 +10,21 @@ type HostGenFunc func() (host string) // SystemResolvers helps to work with local resolvers' addresses provided by OS. type SystemResolvers interface { - // Get returns the slice of local resolvers' addresses. It should be - // safe for concurrent use. + // Get returns the slice of local resolvers' addresses. It must be safe for + // concurrent use. Get() (rs []string) - // refresh refreshes the local resolvers' addresses cache. It should be - // safe for concurrent use. + // refresh refreshes the local resolvers' addresses cache. It must be safe + // for concurrent use. refresh() (err error) } -// refreshWithTicker refreshes the cache of sr after each tick form tickCh. -func refreshWithTicker(sr SystemResolvers, tickCh <-chan time.Time) { - defer log.OnPanic("systemResolvers") - - // TODO(e.burkov): Implement a functionality to stop ticker. - for range tickCh { - err := sr.refresh() - if err != nil { - log.Error("systemResolvers: error in refreshing goroutine: %s", err) - - continue - } - - log.Debug("systemResolvers: local addresses cache is refreshed") - } -} - // 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( - refreshIvl time.Duration, hostGenFunc HostGenFunc, ) (sr SystemResolvers, err error) { - sr = newSystemResolvers(refreshIvl, hostGenFunc) + sr = newSystemResolvers(hostGenFunc) // Fill cache. err = sr.refresh() @@ -56,11 +32,5 @@ func NewSystemResolvers( return nil, err } - if refreshIvl > 0 { - ticker := time.NewTicker(refreshIvl) - - go refreshWithTicker(sr, ticker.C) - } - return sr, nil } diff --git a/internal/aghnet/systemresolvers_others.go b/internal/aghnet/systemresolvers_others.go index 8acdb6c7..f8afa286 100644 --- a/internal/aghnet/systemresolvers_others.go +++ b/internal/aghnet/systemresolvers_others.go @@ -24,12 +24,15 @@ func defaultHostGen() (host string) { // systemResolvers is a default implementation of SystemResolvers interface. type systemResolvers struct { - resolver *net.Resolver - hostGenFunc HostGenFunc - - // addrs is the set that contains cached local resolvers' addresses. - addrs *stringutil.Set + // addrsLock protects addrs. addrsLock sync.RWMutex + // addrs is the set that contains cached local resolvers' addresses. + addrs *stringutil.Set + + // resolver is used to fetch the resolvers' addresses. + resolver *net.Resolver + // hostGenFunc generates hosts to resolve. + hostGenFunc HostGenFunc } const ( @@ -44,6 +47,7 @@ const ( errUnexpectedHostFormat errors.Error = "unexpected host format" ) +// refresh implements the SystemResolvers interface for *systemResolvers. func (sr *systemResolvers) refresh() (err error) { defer func() { err = errors.Annotate(err, "systemResolvers: %w") }() @@ -56,7 +60,7 @@ func (sr *systemResolvers) refresh() (err error) { return err } -func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr SystemResolvers) { +func newSystemResolvers(hostGenFunc HostGenFunc) (sr SystemResolvers) { if hostGenFunc == nil { hostGenFunc = defaultHostGen } @@ -76,19 +80,18 @@ func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr S func validateDialedHost(host string) (err error) { defer func() { err = errors.Annotate(err, "parsing %q: %w", host) }() - var ipStr string parts := strings.Split(host, "%") switch len(parts) { case 1: - ipStr = host + // host case 2: // Remove the zone and check the IP address part. - ipStr = parts[0] + host = parts[0] default: return errUnexpectedHostFormat } - if net.ParseIP(ipStr) == nil { + if _, err = netutil.ParseIP(host); err != nil { return errBadAddrPassed } diff --git a/internal/aghnet/systemresolvers_others_test.go b/internal/aghnet/systemresolvers_others_test.go index 79abeca2..f7cf9ef0 100644 --- a/internal/aghnet/systemresolvers_others_test.go +++ b/internal/aghnet/systemresolvers_others_test.go @@ -6,37 +6,32 @@ package aghnet import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func createTestSystemResolversImp( +func createTestSystemResolversImpl( t *testing.T, - refreshDur time.Duration, hostGenFunc HostGenFunc, ) (imp *systemResolvers) { t.Helper() - sr := createTestSystemResolvers(t, refreshDur, hostGenFunc) + sr := createTestSystemResolvers(t, hostGenFunc) + require.IsType(t, (*systemResolvers)(nil), sr) - var ok bool - imp, ok = sr.(*systemResolvers) - require.True(t, ok) - - return imp + return sr.(*systemResolvers) } func TestSystemResolvers_Refresh(t *testing.T) { t.Run("expected_error", func(t *testing.T) { - sr := createTestSystemResolvers(t, 0, nil) + sr := createTestSystemResolvers(t, nil) assert.NoError(t, sr.refresh()) }) t.Run("unexpected_error", func(t *testing.T) { - _, err := NewSystemResolvers(0, func() string { + _, err := NewSystemResolvers(func() string { return "127.0.0.1::123" }) assert.Error(t, err) @@ -44,7 +39,7 @@ func TestSystemResolvers_Refresh(t *testing.T) { } func TestSystemResolvers_DialFunc(t *testing.T) { - imp := createTestSystemResolversImp(t, 0, nil) + imp := createTestSystemResolversImpl(t, nil) testCases := []struct { want error @@ -52,7 +47,7 @@ func TestSystemResolvers_DialFunc(t *testing.T) { address string }{{ want: errFakeDial, - name: "valid", + name: "valid_ipv4", address: "127.0.0.1", }, { want: errFakeDial, diff --git a/internal/aghnet/systemresolvers_test.go b/internal/aghnet/systemresolvers_test.go index 13145817..0a19490d 100644 --- a/internal/aghnet/systemresolvers_test.go +++ b/internal/aghnet/systemresolvers_test.go @@ -2,7 +2,6 @@ package aghnet import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -10,13 +9,12 @@ import ( func createTestSystemResolvers( t *testing.T, - refreshDur time.Duration, hostGenFunc HostGenFunc, ) (sr SystemResolvers) { t.Helper() var err error - sr, err = NewSystemResolvers(refreshDur, hostGenFunc) + sr, err = NewSystemResolvers(hostGenFunc) require.NoError(t, err) require.NotNil(t, sr) @@ -24,8 +22,14 @@ func createTestSystemResolvers( } func TestSystemResolvers_Get(t *testing.T) { - sr := createTestSystemResolvers(t, 0, nil) - assert.NotEmpty(t, sr.Get()) + sr := createTestSystemResolvers(t, nil) + + var rs []string + require.NotPanics(t, func() { + rs = sr.Get() + }) + + assert.NotEmpty(t, rs) } // TODO(e.burkov): Write tests for refreshWithTicker. diff --git a/internal/aghnet/systemresolvers_windows.go b/internal/aghnet/systemresolvers_windows.go index 5acdfa85..f82d6e7e 100644 --- a/internal/aghnet/systemresolvers_windows.go +++ b/internal/aghnet/systemresolvers_windows.go @@ -11,7 +11,6 @@ import ( "os/exec" "strings" "sync" - "time" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" @@ -27,7 +26,7 @@ type systemResolvers struct { addrsLock sync.RWMutex } -func newSystemResolvers(refreshIvl time.Duration, _ HostGenFunc) (sr SystemResolvers) { +func newSystemResolvers(_ HostGenFunc) (sr SystemResolvers) { return &systemResolvers{} } diff --git a/internal/aghnet/testdata/proc_net_arp b/internal/aghnet/testdata/proc_net_arp new file mode 100644 index 00000000..8460c8bb --- /dev/null +++ b/internal/aghnet/testdata/proc_net_arp @@ -0,0 +1,6 @@ +IP address HW type Flags HW address Mask Device +192.168.1.2 0x1 0x2 ab:cd:ef:ab:cd:ef * wan +::ffff:ffff 0x1 0x0 ef:cd:ab:ef:cd:ab * br-lan +0.0.0.0 0x0 0x0 00:00:00:00:00:00 * unspec +1.2.3.4.5 0x1 0x2 aa:bb:cc:dd:ee:ff * wan +1.2.3.4 0x1 0x2 12:34:56:78:910 * wan \ No newline at end of file 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/aghos/os.go b/internal/aghos/os.go index 29eb1afc..3b688749 100644 --- a/internal/aghos/os.go +++ b/internal/aghos/os.go @@ -52,24 +52,27 @@ func HaveAdminRights() (bool, error) { return haveAdminRights() } -// MaxCmdOutputSize is the maximum length of performed shell command output. -const MaxCmdOutputSize = 2 * 1024 +// MaxCmdOutputSize is the maximum length of performed shell command output in +// bytes. +const MaxCmdOutputSize = 64 * 1024 // RunCommand runs shell command. -func RunCommand(command string, arguments ...string) (int, string, error) { +func RunCommand(command string, arguments ...string) (code int, output []byte, err error) { cmd := exec.Command(command, arguments...) out, err := cmd.Output() if len(out) > MaxCmdOutputSize { out = out[:MaxCmdOutputSize] } - if errors.As(err, new(*exec.ExitError)) { - return cmd.ProcessState.ExitCode(), string(out), nil - } else if err != nil { - return 1, "", fmt.Errorf("exec.Command(%s) failed: %w: %s", command, err, string(out)) + if err != nil { + if eerr := new(exec.ExitError); errors.As(err, &eerr) { + return eerr.ExitCode(), eerr.Stderr, nil + } + + return 1, nil, fmt.Errorf("command %q failed: %w: %s", command, err, out) } - return cmd.ProcessState.ExitCode(), string(out), nil + return cmd.ProcessState.ExitCode(), out, nil } // PIDByCommand searches for process named command and returns its PID ignoring @@ -172,3 +175,13 @@ func RootDirFS() (fsys fs.FS) { // behavior is undocumented but it currently works. return os.DirFS("") } + +// NotifyShutdownSignal notifies c on receiving shutdown signals. +func NotifyShutdownSignal(c chan<- os.Signal) { + notifyShutdownSignal(c) +} + +// IsShutdownSignal returns true if sig is a shutdown signal. +func IsShutdownSignal(sig os.Signal) (ok bool) { + return isShutdownSignal(sig) +} diff --git a/internal/aghos/os_unix.go b/internal/aghos/os_unix.go new file mode 100644 index 00000000..9a3cc308 --- /dev/null +++ b/internal/aghos/os_unix.go @@ -0,0 +1,27 @@ +//go:build darwin || freebsd || linux || openbsd +// +build darwin freebsd linux openbsd + +package aghos + +import ( + "os" + "os/signal" + + "golang.org/x/sys/unix" +) + +func notifyShutdownSignal(c chan<- os.Signal) { + signal.Notify(c, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM) +} + +func isShutdownSignal(sig os.Signal) (ok bool) { + switch sig { + case + unix.SIGINT, + unix.SIGQUIT, + unix.SIGTERM: + return true + default: + return false + } +} diff --git a/internal/aghos/os_windows.go b/internal/aghos/os_windows.go index bff5a3f0..31fca3ef 100644 --- a/internal/aghos/os_windows.go +++ b/internal/aghos/os_windows.go @@ -4,6 +4,10 @@ package aghos import ( + "os" + "os/signal" + "syscall" + "golang.org/x/sys/windows" ) @@ -35,3 +39,20 @@ func haveAdminRights() (bool, error) { func isOpenWrt() (ok bool) { return false } + +func notifyShutdownSignal(c chan<- os.Signal) { + // syscall.SIGTERM is processed automatically. See go doc os/signal, + // section Windows. + signal.Notify(c, os.Interrupt) +} + +func isShutdownSignal(sig os.Signal) (ok bool) { + switch sig { + case + os.Interrupt, + syscall.SIGTERM: + return true + default: + return false + } +} 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 9a050f52..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" @@ -122,6 +122,7 @@ type FilteringConfig struct { EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests + HandleDDR bool `yaml:"handle_ddr"` // Handle DDR requests // IpsetList is the ipset configuration that allows AdGuard Home to add // IP addresses of the specified domain names to an ipset list. Syntax: @@ -133,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:"-"` @@ -151,7 +153,7 @@ type TLSConfig struct { PrivateKeyData []byte `yaml:"-" json:"-"` // ServerName is the hostname of the server. Currently, it is only being - // used for ClientID checking. + // used for ClientID checking and Discovery of Designated Resolvers (DDR). ServerName string `yaml:"-" json:"-"` cert tls.Certificate @@ -191,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 @@ -276,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() { @@ -287,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 dd5a4dd5..f3c98361 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -76,6 +76,10 @@ const ( resultCodeError ) +// ddrHostFQDN is the FQDN used in Discovery of Designated Resolvers (DDR) requests. +// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html. +const ddrHostFQDN = "_dns.resolver.arpa." + // handleDNSRequest filters the incoming DNS requests and writes them to the query log func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { ctx := &dnsContext{ @@ -94,10 +98,11 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { mods := []modProcessFunc{ s.processRecursion, s.processInitial, + s.processDDRQuery, s.processDetermineLocal, - s.processInternalHosts, + s.processDHCPHosts, s.processRestrictLocal, - s.processInternalIPAddrs, + s.processDHCPAddrs, s.processFilteringBeforeRequest, s.processLocalPTR, s.processUpstream, @@ -135,7 +140,6 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) { pctx.Res = s.genNXDomain(pctx.Req) return resultCodeFinish - } return resultCodeSuccess @@ -226,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 } @@ -242,6 +244,98 @@ func (s *Server) onDHCPLeaseChanged(flags int) { s.setTableIPToHost(ipToHost) } +// processDDRQuery responds to SVCB query for a special use domain name +// ‘_dns.resolver.arpa’. The result contains different types of encryption +// supported by current user configuration. +// +// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html. +func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) { + d := ctx.proxyCtx + question := d.Req.Question[0] + + if !s.conf.HandleDDR { + return resultCodeSuccess + } + + if question.Name == ddrHostFQDN { + 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 + } + + d.Res = s.makeDDRResponse(d.Req) + + return resultCodeFinish + } + + return resultCodeSuccess +} + +// 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) + // 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.conf.HTTPSListenAddrs { + values := []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"h2"}}, + &dns.SVCBPort{Port: uint16(addr.Port)}, + &dns.SVCBDoHPath{Template: "/dns-query?dns"}, + } + + ans := &dns.SVCB{ + Hdr: s.hdr(req, dns.TypeSVCB), + Priority: 1, + Target: domainName, + Value: values, + } + + resp.Answer = append(resp.Answer, ans) + } + + for _, addr := range s.dnsProxy.TLSListenAddr { + values := []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"dot"}}, + &dns.SVCBPort{Port: uint16(addr.Port)}, + } + + ans := &dns.SVCB{ + Hdr: s.hdr(req, dns.TypeSVCB), + 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, + } + + resp.Answer = append(resp.Answer, ans) + } + + return resp +} + // processDetermineLocal determines if the client's IP address is from // locally-served network and saves the result into the context. func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) { @@ -252,7 +346,7 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) { return rc } - dctx.isLocalClient = s.subnetDetector.IsLocallyServedNetwork(ip) + dctx.isLocalClient = s.privateNets.Contains(ip) return rc } @@ -280,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 } @@ -299,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 } @@ -316,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. @@ -373,8 +466,8 @@ 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. - if !s.subnetDetector.IsLocallyServedNetwork(ip) { + // don't need to be accessible externally. + if !s.privateNets.Contains(ip) { log.Debug("dns: addr %s is not from locally-served network", ip) return resultCodeSuccess @@ -413,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 @@ -430,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 @@ -481,7 +574,7 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) { s.serverLock.RLock() defer s.serverLock.RUnlock() - if !s.subnetDetector.IsLocallyServedNetwork(ip) { + if !s.privateNets.Contains(ip) { return resultCodeSuccess } diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index 4fc87ccf..25e28afd 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -4,35 +4,212 @@ import ( "net" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestServer_ProcessDetermineLocal(t *testing.T) { - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - s := &Server{ - subnetDetector: snd, +const ( + ddrTestDomainName = "dns.example.net" + ddrTestFQDN = ddrTestDomainName + "." +) + +func TestServer_ProcessDDRQuery(t *testing.T) { + dohSVCB := &dns.SVCB{ + Priority: 1, + Target: ddrTestFQDN, + Value: []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"h2"}}, + &dns.SVCBPort{Port: 8044}, + &dns.SVCBDoHPath{Template: "/dns-query?dns"}, + }, + } + + dotSVCB := &dns.SVCB{ + 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 + want []*dns.SVCB + wantRes resultCode + portDoH int + portDoT int + portDoQ int + qtype uint16 + ddrEnabled bool + }{{ + name: "pass_host", + wantRes: resultCodeSuccess, + host: "example.net.", + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoH: 8043, + }, { + name: "pass_qtype", + wantRes: resultCodeFinish, + host: ddrHostFQDN, + qtype: dns.TypeA, + ddrEnabled: true, + portDoH: 8043, + }, { + name: "pass_disabled_tls", + wantRes: resultCodeFinish, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + }, { + name: "pass_disabled_ddr", + wantRes: resultCodeSuccess, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: false, + portDoH: 8043, + }, { + name: "dot", + wantRes: resultCodeFinish, + want: []*dns.SVCB{dotSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoT: 8043, + }, { + name: "doh", + wantRes: resultCodeFinish, + want: []*dns.SVCB{dohSVCB}, + host: ddrHostFQDN, + 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, + want: []*dns.SVCB{dotSVCB, dohSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoT: 8043, + portDoH: 8044, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.portDoQ, tc.ddrEnabled) + + req := createTestMessageWithType(tc.host, tc.qtype) + + dctx := &dnsContext{ + proxyCtx: &proxy.DNSContext{ + Req: req, + }, + } + + res := s.processDDRQuery(dctx) + require.Equal(t, tc.wantRes, res) + + if tc.wantRes != resultCodeFinish { + return + } + + msg := dctx.proxyCtx.Res + require.NotNil(t, msg) + + for _, v := range tc.want { + v.Hdr = s.hdr(req, dns.TypeSVCB) + } + + assert.ElementsMatch(t, tc.want, msg.Answer) + }) + } +} + +func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) { + t.Helper() + + proxyConf := proxy.Config{} + + 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, + }, + conf: ServerConfig{ + FilteringConfig: FilteringConfig{ + HandleDDR: ddrEnabled, + }, + TLSConfig: TLSConfig{ + ServerName: ddrTestDomainName, + }, + }, + } + + if portDoH > 0 { + s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}} + } + + return s +} + +func TestServer_ProcessDetermineLocal(t *testing.T) { + s := &Server{ + privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), + } + + testCases := []struct { + want assert.BoolAssertionFunc name string cliIP net.IP - want bool }{{ + want: assert.True, name: "local", cliIP: net.IP{192, 168, 0, 1}, - want: true, }, { + want: assert.False, name: "external", cliIP: net.IP{250, 249, 0, 1}, - want: false, + }, { + want: assert.False, + name: "invalid", + cliIP: net.IP{1, 2, 3, 4, 5}, + }, { + want: assert.False, + name: "nil", + cliIP: nil, }} for _, tc := range testCases { @@ -47,12 +224,12 @@ func TestServer_ProcessDetermineLocal(t *testing.T) { } s.processDetermineLocal(dctx) - assert.Equal(t, tc.want, dctx.isLocalClient) + tc.want(t, dctx.isLocalClient) }) } } -func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { +func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { knownIP := net.IP{1, 2, 3, 4} testCases := []struct { @@ -93,7 +270,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) { dhcpServer: &testDHCP{}, localDomainSuffix: defaultLocalDomainSuffix, tableHostToIP: hostToIPTable{ - "example": knownIP, + "example." + defaultLocalDomainSuffix: knownIP, }, } @@ -115,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 { @@ -141,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} @@ -193,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 2d32cfd2..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 @@ -74,7 +69,7 @@ type Server struct { localDomainSuffix string ipset ipsetCtx - subnetDetector *aghnet.SubnetDetector + privateNets netutil.SubnetSet localResolvers *proxy.Proxy sysResolvers aghnet.SystemResolvers recDetector *recursionDetector @@ -107,28 +102,17 @@ 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 - QueryLog querylog.QueryLog - DHCPServer dhcpd.ServerInterface - SubnetDetector *aghnet.SubnetDetector - Anonymizer *aghnet.IPMut - 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) + DNSFilter *filtering.DNSFilter + Stats stats.Interface + QueryLog querylog.QueryLog + DHCPServer dhcpd.ServerInterface + PrivateNets netutil.SubnetSet + Anonymizer *aghnet.IPMut + LocalDomain string } const ( @@ -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 { @@ -161,7 +145,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) { dnsFilter: p.DNSFilter, stats: p.Stats, queryLog: p.QueryLog, - subnetDetector: p.SubnetDetector, + privateNets: p.PrivateNets, localDomainSuffix: localDomainSuffix, recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum), clientIDCache: cache.New(cache.Config{ @@ -173,7 +157,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) { // TODO(e.burkov): Enable the refresher after the actual implementation // passes the public testing. - s.sysResolvers, err = aghnet.NewSystemResolvers(0, nil) + s.sysResolvers, err = aghnet.NewSystemResolvers(nil) if err != nil { return nil, fmt.Errorf("initializing system resolvers: %w", err) } @@ -314,14 +298,16 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) { StartTime: time.Now(), } - resolver := s.internalProxy - if s.subnetDetector.IsLocallyServedNetwork(ip) { + var resolver *proxy.Proxy + if s.privateNets.Contains(ip) { if !s.conf.UsePrivateRDNS { return "", nil } resolver = s.localResolvers s.recDetector.add(*req) + } else { + resolver = s.internalProxy } if err = resolver.Resolve(ctx); err != nil { diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index bc90d760..5144680b 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -17,13 +17,14 @@ 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" "github.com/miekg/dns" @@ -69,14 +70,11 @@ func createTestServer( f := filtering.New(filterConf, filters) f.SetEnabled(true) - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) - + var err error s, err = NewServer(DNSCreateParams{ - DHCPServer: &testDHCP{}, - DNSFilter: f, - SubnetDetector: snd, + DHCPServer: &testDHCP{}, + DNSFilter: f, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) @@ -770,16 +768,11 @@ func TestBlockedCustomIP(t *testing.T) { Data: []byte(rules), }} - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) - f := filtering.New(&filtering.Config{}, filters) - var s *Server - s, err = NewServer(DNSCreateParams{ - DHCPServer: &testDHCP{}, - DNSFilter: f, - SubnetDetector: snd, + s, err := NewServer(DNSCreateParams{ + DHCPServer: &testDHCP{}, + DNSFilter: f, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) @@ -860,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{ @@ -913,15 +903,10 @@ func TestRewrite(t *testing.T) { f := filtering.New(c, nil) f.SetEnabled(true) - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) - - var s *Server - s, err = NewServer(DNSCreateParams{ - DHCPServer: &testDHCP{}, - DNSFilter: f, - SubnetDetector: snd, + s, err := NewServer(DNSCreateParams{ + DHCPServer: &testDHCP{}, + DNSFilter: f, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) @@ -1000,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 @@ -1028,36 +1013,33 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} func TestPTRResponseFromDHCPLeases(t *testing.T) { - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) + const localDomain = "lan" - var s *Server - s, err = NewServer(DNSCreateParams{ - DNSFilter: filtering.New(&filtering.Config{}, nil), - DHCPServer: &testDHCP{}, - SubnetDetector: snd, + 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) @@ -1066,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) { @@ -1105,16 +1087,11 @@ func TestPTRResponseFromHosts(t *testing.T) { }, nil) flt.SetEnabled(true) - var snd *aghnet.SubnetDetector - snd, err = aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) - var s *Server s, err = NewServer(DNSCreateParams{ - DHCPServer: &testDHCP{}, - DNSFilter: flt, - SubnetDetector: snd, + DHCPServer: &testDHCP{}, + DNSFilter: flt, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) @@ -1197,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{ @@ -1227,11 +1227,8 @@ func TestServer_Exchange(t *testing.T) { srv.conf.ResolveClients = true srv.conf.UsePrivateRDNS = true - var err error - srv.subnetDetector, err = aghnet.NewSubnetDetector() - require.NoError(t, err) + srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed) - localIP := net.IP{192, 168, 1, 1} testCases := []struct { name string want string @@ -1240,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/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go index 347b8f29..5ba10582 100644 --- a/internal/dnsforward/dnsrewrite_test.go +++ b/internal/dnsforward/dnsrewrite_test.go @@ -22,7 +22,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) { Preference: 32, } svcbVal := &rules.DNSSVCB{ - Params: map[string]string{"alpn": "h3"}, + Params: map[string]string{"alpn": "h3", "dohpath": "/dns-query"}, Target: dns.Fqdn(domain), Priority: 32, } @@ -164,10 +164,20 @@ func TestServer_FilterDNSRewrite(t *testing.T) { require.Len(t, d.Res.Answer, 1) ans, ok := d.Res.Answer[0].(*dns.SVCB) - require.True(t, ok) - assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) - assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String()) + require.True(t, ok) + require.Len(t, ans.Value, 2) + + assert.ElementsMatch( + t, + []dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH}, + []dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()}, + ) + assert.ElementsMatch( + t, + []string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]}, + []string{ans.Value[0].String(), ans.Value[1].String()}, + ) assert.Equal(t, svcbVal.Target, ans.Target) assert.Equal(t, svcbVal.Priority, ans.Priority) }) @@ -186,8 +196,18 @@ func TestServer_FilterDNSRewrite(t *testing.T) { ans, ok := d.Res.Answer[0].(*dns.HTTPS) require.True(t, ok) - assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key()) - assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String()) + require.Len(t, ans.Value, 2) + + assert.ElementsMatch( + t, + []dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH}, + []dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()}, + ) + assert.ElementsMatch( + t, + []string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]}, + []string{ans.Value[0].String(), ans.Value[1].String()}, + ) assert.Equal(t, svcbVal.Target, ans.Target) assert.Equal(t, svcbVal.Priority, ans.Priority) }) diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 84570bce..69dcf8f9 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -4,7 +4,6 @@ import ( "net" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" @@ -39,14 +38,10 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { f := filtering.New(&filtering.Config{}, filters) f.SetEnabled(true) - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) - require.NotNil(t, snd) - s, err := NewServer(DNSCreateParams{ - DHCPServer: &testDHCP{}, - DNSFilter: f, - SubnetDetector: snd, + DHCPServer: &testDHCP{}, + DNSFilter: f, + PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), }) require.NoError(t, err) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index d653fa1e..93121fac 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -5,12 +5,10 @@ import ( "fmt" "net" "net/http" - "sort" "strings" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" - "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/errors" @@ -18,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 { @@ -167,7 +167,7 @@ func (req *dnsConfig) checkBootstrap() (err error) { } // validate returns an error if any field of req is invalid. -func (req *dnsConfig) validate(snd *aghnet.SubnetDetector) (err error) { +func (req *dnsConfig) validate(privateNets netutil.SubnetSet) (err error) { if req.Upstreams != nil { err = ValidateUpstreams(*req.Upstreams) if err != nil { @@ -176,7 +176,7 @@ func (req *dnsConfig) validate(snd *aghnet.SubnetDetector) (err error) { } if req.LocalPTRUpstreams != nil { - err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, snd) + err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, privateNets) if err != nil { return fmt.Errorf("validating private upstream servers: %w", err) } @@ -224,7 +224,7 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { return } - err = req.validate(s.subnetDetector) + err = req.validate(s.privateNets) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) @@ -350,17 +350,6 @@ func IsCommentOrEmpty(s string) (ok bool) { return len(s) == 0 || s[0] == '#' } -// LocalNetChecker is used to check if the IP address belongs to a local -// network. -type LocalNetChecker interface { - // IsLocallyServedNetwork returns true if ip is contained in any of address - // registries defined by RFC 6303. - IsLocallyServedNetwork(ip net.IP) (ok bool) -} - -// type check -var _ LocalNetChecker = (*aghnet.SubnetDetector)(nil) - // newUpstreamConfig validates upstreams and returns an appropriate upstream // configuration or nil if it can't be built. // @@ -375,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}, @@ -385,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 } @@ -405,25 +402,11 @@ 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 -// a locally-served network. lnc must not be nil. -func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err error) { +// a locally-served network. privateNets must not be nil. +func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) { conf, err := newUpstreamConfig(upstreams) if err != nil { return err @@ -433,9 +416,11 @@ func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err erro 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 { @@ -444,7 +429,7 @@ func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err erro continue } - if !lnc.IsLocallyServedNetwork(subnet.IP) { + if !privateNets.Contains(subnet.IP) { errs = append( errs, fmt.Errorf("arpa domain %q should point to a locally-served network", domain), @@ -459,18 +444,16 @@ func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err erro return nil } -var protocols = []string{"tls://", "https://", "tcp://", "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 - } +var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"} +// 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 } @@ -497,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:], "/]") @@ -510,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, }}, @@ -552,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 @@ -565,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, }}, @@ -585,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 } @@ -647,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 } @@ -662,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 66931f4d..8863c6c6 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -14,6 +14,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,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 @@ -184,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", @@ -255,104 +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.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 @@ -367,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", @@ -377,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", @@ -389,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 { @@ -402,8 +344,7 @@ func TestValidateUpstreams(t *testing.T) { } func TestValidateUpstreamsPrivate(t *testing.T) { - snd, err := aghnet.NewSubnetDetector() - require.NoError(t, err) + ss := netutil.SubnetSetFunc(netutil.IsLocallyServed) testCases := []struct { name string @@ -444,7 +385,7 @@ func TestValidateUpstreamsPrivate(t *testing.T) { set := []string{"192.168.0.1", tc.u} t.Run(tc.name, func(t *testing.T) { - err = ValidateUpstreamsPrivate(set, snd) + err := ValidateUpstreamsPrivate(set, ss) testutil.AssertErrorMsg(t, tc.wantErr, err) }) } diff --git a/internal/dnsforward/recursiondetector_test.go b/internal/dnsforward/recursiondetector_test.go index 7573b668..4edb3a37 100644 --- a/internal/dnsforward/recursiondetector_test.go +++ b/internal/dnsforward/recursiondetector_test.go @@ -83,7 +83,7 @@ func TestRecursionDetector_Suspect(t *testing.T) { testCases := []struct { name string msg dns.Msg - want bool + want int }{{ name: "simple", msg: dns.Msg{ @@ -95,24 +95,18 @@ func TestRecursionDetector_Suspect(t *testing.T) { Qtype: dns.TypeA, }}, }, - want: true, + want: 1, }, { name: "unencumbered", msg: dns.Msg{}, - want: false, + want: 0, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Cleanup(rd.clear) - rd.add(tc.msg) - - if tc.want { - assert.Equal(t, 1, rd.recentRequests.Stats().Count) - } else { - assert.Zero(t, rd.recentRequests.Stats().Count) - } + assert.Equal(t, tc.want, rd.recentRequests.Stats().Count) }) } } diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index d113a5fd..9a7b1ddb 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -41,55 +41,65 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) { // uninitialized while in use. This can happen after proxy server has been // stopped, but its workers haven't yet exited. if shouldLog && s.queryLog != nil { - p := &querylog.AddParams{ - Question: msg, - Answer: pctx.Res, - OrigAnswer: dctx.origResp, - Result: dctx.result, - Elapsed: elapsed, - ClientID: dctx.clientID, - ClientIP: ip, - AuthenticatedData: dctx.responseAD, - } - - switch pctx.Proto { - case proxy.ProtoHTTPS: - p.ClientProto = querylog.ClientProtoDoH - case proxy.ProtoQUIC: - p.ClientProto = querylog.ClientProtoDoQ - case proxy.ProtoTLS: - p.ClientProto = querylog.ClientProtoDoT - case proxy.ProtoDNSCrypt: - p.ClientProto = querylog.ClientProtoDNSCrypt - default: - // Consider this a plain DNS-over-UDP or DNS-over-TCP request. - } - - if pctx.Upstream != nil { - p.Upstream = pctx.Upstream.Address() - } else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" { - p.Upstream = pctx.CachedUpstreamAddr - p.Cached = true - } - - s.queryLog.Add(p) + s.logQuery(dctx, pctx, elapsed, ip) } - s.updateStats(dctx, elapsed, *dctx.result, ip) + if s.stats != nil { + s.updateStats(dctx, elapsed, *dctx.result, ip) + } return resultCodeSuccess } +// logQuery pushes the request details into the query log. +func (s *Server) logQuery( + dctx *dnsContext, + pctx *proxy.DNSContext, + elapsed time.Duration, + ip net.IP, +) { + p := &querylog.AddParams{ + Question: pctx.Req, + ReqECS: pctx.ReqECS, + Answer: pctx.Res, + OrigAnswer: dctx.origResp, + Result: dctx.result, + ClientID: dctx.clientID, + ClientIP: ip, + Elapsed: elapsed, + AuthenticatedData: dctx.responseAD, + } + + switch pctx.Proto { + case proxy.ProtoHTTPS: + p.ClientProto = querylog.ClientProtoDoH + case proxy.ProtoQUIC: + p.ClientProto = querylog.ClientProtoDoQ + case proxy.ProtoTLS: + p.ClientProto = querylog.ClientProtoDoT + case proxy.ProtoDNSCrypt: + p.ClientProto = querylog.ClientProtoDNSCrypt + default: + // Consider this a plain DNS-over-UDP or DNS-over-TCP request. + } + + if pctx.Upstream != nil { + p.Upstream = pctx.Upstream.Address() + } else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" { + p.Upstream = pctx.CachedUpstreamAddr + p.Cached = true + } + + s.queryLog.Add(p) +} + +// updatesStats writes the request into statistics. func (s *Server) updateStats( ctx *dnsContext, elapsed time.Duration, res filtering.Result, clientIP net.IP, ) { - if s.stats == nil { - return - } - pctx := ctx.proxyCtx e := stats.Entry{} e.Domain = strings.ToLower(pctx.Req.Question[0].Name) 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/dnsforward/svcbmsg.go b/internal/dnsforward/svcbmsg.go index 99413ed3..d1e3e3d8 100644 --- a/internal/dnsforward/svcbmsg.go +++ b/internal/dnsforward/svcbmsg.go @@ -32,12 +32,16 @@ func (s *Server) genAnswerHTTPS(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.HTT // github.com/miekg/dns module. var strToSVCBKey = map[string]dns.SVCBKey{ "alpn": dns.SVCB_ALPN, - "echconfig": dns.SVCB_ECHCONFIG, + "ech": dns.SVCB_ECHCONFIG, "ipv4hint": dns.SVCB_IPV4HINT, "ipv6hint": dns.SVCB_IPV6HINT, "mandatory": dns.SVCB_MANDATORY, "no-default-alpn": dns.SVCB_NO_DEFAULT_ALPN, "port": dns.SVCB_PORT, + + // TODO(a.garipov): This is the previous name for the parameter that has + // since been changed. Remove this in v0.109.0. + "echconfig": dns.SVCB_ECHCONFIG, } // svcbKeyHandler is a handler for one SVCB parameter key. @@ -51,10 +55,10 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{ } }, - "echconfig": func(valStr string) (val dns.SVCBKeyValue) { + "ech": func(valStr string) (val dns.SVCBKeyValue) { ech, err := base64.StdEncoding.DecodeString(valStr) if err != nil { - log.Debug("can't parse svcb/https echconfig: %s; ignoring", err) + log.Debug("can't parse svcb/https ech: %s; ignoring", err) return nil } @@ -119,6 +123,32 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{ Port: uint16(port64), } }, + + // TODO(a.garipov): This is the previous name for the parameter that has + // since been changed. Remove this in v0.109.0. + "echconfig": func(valStr string) (val dns.SVCBKeyValue) { + log.Info( + `warning: svcb/https record parameter name "echconfig" is deprecated; ` + + `use "ech" instead`, + ) + + ech, err := base64.StdEncoding.DecodeString(valStr) + if err != nil { + log.Debug("can't parse svcb/https ech: %s; ignoring", err) + + return nil + } + + return &dns.SVCBECHConfig{ + ECH: ech, + } + }, + + "dohpath": func(valStr string) (val dns.SVCBKeyValue) { + return &dns.SVCBDoHPath{ + Template: valStr, + } + }, } // genAnswerSVCB returns a properly initialized SVCB resource record. diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go index db6994c3..8de53988 100644 --- a/internal/dnsforward/svcbmsg_test.go +++ b/internal/dnsforward/svcbmsg_test.go @@ -87,14 +87,18 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) { svcb: dnssvcb("alpn", "h3"), want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}), name: "alpn", + }, { + svcb: dnssvcb("ech", "AAAA"), + want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}), + name: "ech", }, { svcb: dnssvcb("echconfig", "AAAA"), want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}), - name: "echconfig", + name: "ech_deprecated", }, { svcb: dnssvcb("echconfig", "%BAD%"), want: wantsvcb(nil), - name: "echconfig_invalid", + name: "ech_invalid", }, { svcb: dnssvcb("ipv4hint", "127.0.0.1"), want: wantsvcb(&dns.SVCBIPv4Hint{Hint: []net.IP{ip4}}), @@ -123,6 +127,10 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) { svcb: dnssvcb("no-default-alpn", ""), want: wantsvcb(&dns.SVCBNoDefaultAlpn{}), name: "no_default_alpn", + }, { + svcb: dnssvcb("dohpath", "/dns-query"), + want: wantsvcb(&dns.SVCBDoHPath{Template: "/dns-query"}), + name: "dohpath", }, { svcb: dnssvcb("port", "8080"), want: wantsvcb(&dns.SVCBPort{Port: 8080}), diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go index 0efd3998..36f8b446 100644 --- a/internal/filtering/blocked.go +++ b/internal/filtering/blocked.go @@ -7,96 +7,181 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" + "golang.org/x/exp/slices" ) -var serviceRules map[string][]*rules.NetworkRule // service name -> filtering rules - +// svc represents a single blocked service. type svc struct { name string rules []string } +// servicesData contains raw blocked service data. +// // Keep in sync with: -// client/src/helpers/constants.js -// client/src/components/ui/Icons.js -var serviceRulesArray = []svc{ - {"whatsapp", []string{"||whatsapp.net^", "||whatsapp.com^"}}, - {"facebook", []string{ +// - client/src/helpers/constants.js +// - client/src/components/ui/Icons.js +var servicesData = []svc{{ + name: "whatsapp", + rules: []string{ + "||wa.me^", + "||whatsapp.com^", + "||whatsapp.net^", + }, +}, { + name: "facebook", + rules: []string{ "||facebook.com^", "||facebook.net^", "||fbcdn.net^", "||accountkit.com^", "||fb.me^", "||fb.com^", + "||fb.gg^", "||fbsbx.com^", + "||fbwat.ch^", "||messenger.com^", "||facebookcorewwwi.onion^", "||fbcdn.com^", "||fb.watch^", - }}, - {"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}}, - {"youtube", []string{ - "||youtube.com^", - "||ytimg.com^", - "||youtu.be^", + }, +}, { + name: "twitter", + rules: []string{ + "||t.co^", + "||twimg.com^", + "||twitter.com^", + "||twttr.com^", + }, +}, { + name: "youtube", + rules: []string{ "||googlevideo.com^", - "||youtubei.googleapis.com^", - "||youtube-nocookie.com^", + "||wide-youtube.l.google.com^", + "||youtu.be^", "||youtube", - }}, - {"twitch", []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}}, - {"netflix", []string{"||nflxext.com^", "||netflix.com^", "||nflximg.net^", "||nflxvideo.net^", "||nflxso.net^"}}, - {"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}}, - {"snapchat", []string{ + "||youtube-nocookie.com^", + "||youtube.com^", + "||youtubei.googleapis.com^", + "||youtubekids.com^", + "||ytimg.com^", + }, +}, { + name: "twitch", + rules: []string{ + "||jtvnw.net^", + "||ttvnw.net^", + "||twitch.tv^", + "||twitchcdn.net^", + }, +}, { + name: "netflix", + rules: []string{ + "||nflxext.com^", + "||netflix.com^", + "||nflximg.net^", + "||nflxvideo.net^", + "||nflxso.net^", + }, +}, { + name: "instagram", + rules: []string{"||instagram.com^", "||cdninstagram.com^"}, +}, { + name: "snapchat", + rules: []string{ "||snapchat.com^", "||sc-cdn.net^", "||snap-dev.net^", "||snapkit.co", "||snapads.com^", "||impala-media-production.s3.amazonaws.com^", - }}, - {"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^", "||discord.com^", "||discord.media^"}}, - {"ok", []string{"||ok.ru^"}}, - {"skype", []string{"||skype.com^", "||skypeassets.com^"}}, - {"vk", []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}}, - {"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}}, - {"steam", []string{ + }, +}, { + name: "discord", + rules: []string{ + "||discord.gg^", + "||discordapp.net^", + "||discordapp.com^", + "||discord.com^", + "||discord.gift", + "||discord.media^", + }, +}, { + name: "ok", + rules: []string{"||ok.ru^"}, +}, { + 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{ + "||userapi.com^", + "||vk-cdn.net^", + "||vk.com^", + "||vkuservideo.net^", + }, +}, { + name: "origin", + rules: []string{ + "||accounts.ea.com^", + "||origin.com^", + "||signin.ea.com^", + }, +}, { + name: "steam", + rules: []string{ "||steam.com^", "||steampowered.com^", "||steamcommunity.com^", "||steamstatic.com^", "||steamstore-a.akamaihd.net^", "||steamcdn-a.akamaihd.net^", - }}, - {"epic_games", []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"}}, - {"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}}, - {"mail_ru", []string{"||mail.ru^"}}, - {"cloudflare", []string{ - "||cloudflare.com^", - "||cloudflare-dns.com^", - "||cloudflare.net^", - "||cloudflareinsights.com^", - "||cloudflarestream.com^", - "||cloudflareresolve.com^", - "||cloudflareclient.com^", - "||cloudflarebolt.com^", - "||cloudflarestatus.com^", - "||cloudflare.cn^", - "||one.one^", - "||warp.plus^", + }, +}, { + name: "epic_games", + rules: []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"}, +}, { + name: "reddit", + rules: []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}, +}, { + name: "mail_ru", + rules: []string{"||mail.ru^"}, +}, { + name: "cloudflare", + rules: []string{ "||1.1.1.1^", - "||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^", "||argotunnel.com^", + "||cloudflare-dns.com^", "||cloudflare-ipfs.com^", "||cloudflare-quic.com^", + "||cloudflare.cn^", + "||cloudflare.com^", + "||cloudflare.net^", "||cloudflareapps.com^", + "||cloudflarebolt.com^", + "||cloudflareclient.com^", + "||cloudflareinsights.com^", + "||cloudflareresolve.com^", + "||cloudflarestatus.com^", + "||cloudflarestream.com^", "||cloudflarewarp.com^", + "||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^", + "||one.one^", "||pages.dev^", "||trycloudflare.com^", "||videodelivery.net^", + "||warp.plus^", "||workers.dev^", - }}, - {"amazon", []string{ + }, +}, { + name: "amazon", + rules: []string{ "||amazon.com^", "||media-amazon.com^", "||primevideo.com^", @@ -120,11 +205,14 @@ var serviceRulesArray = []svc{ "||amazon.com.br^", "||amazon.co.jp^", "||amazon.com.mx^", + "||amazon.com.tr^", "||amazon.co.uk^", "||createspace.com^", "||aws", - }}, - {"ebay", []string{ + }, +}, { + name: "ebay", + rules: []string{ "||ebay.com^", "||ebayimg.com^", "||ebaystatic.com^", @@ -150,8 +238,10 @@ var serviceRulesArray = []svc{ "||ebay.com.my^", "||ebay.com.sg^", "||ebay.co.uk^", - }}, - {"tiktok", []string{ + }, +}, { + name: "tiktok", + rules: []string{ "||tiktok.com^", "||tiktokcdn.com^", "||musical.ly^", @@ -165,65 +255,95 @@ 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^", - }}, - {"vimeo", []string{ + "||toutiaovod.com^", + "||douyincdn.com^", + }, +}, { + name: "vimeo", + rules: []string{ + "*vod-adaptive.akamaized.net^", "||vimeo.com^", "||vimeocdn.com^", - "*vod-adaptive.akamaized.net^", - }}, - {"pinterest", []string{ - "||pinterest.*^", + }, +}, { + name: "pinterest", + rules: []string{ "||pinimg.com^", - }}, - {"imgur", []string{ - "||imgur.com^", - }}, - {"dailymotion", []string{ + "||pinterest.*^", + }, +}, { + name: "imgur", + rules: []string{"||imgur.com^"}, +}, { + name: "dailymotion", + rules: []string{ "||dailymotion.com^", "||dm-event.net^", "||dmcdn.net^", - }}, - {"qq", []string{ - // block qq.com and subdomains excluding WeChat domains - "^(?!weixin|wx)([^.]+\\.)?qq\\.com$", + }, +}, { + name: "qq", + rules: []string{ + // Block qq.com and subdomains excluding WeChat's domains. + "||qq.com^$denyallow=wx.qq.com|weixin.qq.com", "||qqzaixian.com^", - }}, - {"wechat", []string{ + "||qq-video.cdn-go.cn^", + "||url.cn^", + }, +}, { + name: "wechat", + rules: []string{ "||wechat.com^", + "||weixin.qq.com.cn^", "||weixin.qq.com^", + "||weixinbridge.com^", "||wx.qq.com^", - }}, - {"viber", []string{ - "||viber.com^", - }}, - {"weibo", []string{ + }, +}, { + name: "viber", + rules: []string{"||viber.com^"}, +}, { + name: "weibo", + rules: []string{ + "||weibo.cn^", "||weibo.com^", - }}, - {"9gag", []string{ + "||weibocdn.com^", + }, +}, { + name: "9gag", + rules: []string{ "||9cache.com^", "||9gag.com^", - }}, - {"telegram", []string{ + }, +}, { + name: "telegram", + rules: []string{ "||t.me^", "||telegram.me^", "||telegram.org^", - }}, - {"disneyplus", []string{ + }, +}, { + name: "disneyplus", + rules: []string{ "||disney-plus.net^", "||disneyplus.com^", "||disney.playback.edge.bamgrid.com^", "||media.dssott.com^", - }}, - {"hulu", []string{ - "||hulu.com^", - }}, - {"spotify", []string{ + }, +}, { + name: "hulu", + rules: []string{"||hulu.com^"}, +}, { + name: "spotify", + rules: []string{ "/_spotify-connect._tcp.local/", "||spotify.com^", "||scdn.co^", @@ -235,29 +355,59 @@ var serviceRulesArray = []svc{ "||audio4-ak-spotify-com.akamaized.net^", "||heads-ak-spotify-com.akamaized.net^", "||heads4-ak-spotify-com.akamaized.net^", - }}, - {"tinder", []string{ + }, +}, { + name: "tinder", + rules: []string{ "||gotinder.com^", "||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 +// serviceRules maps a service ID to its filtering rules. +var serviceRules map[string][]*rules.NetworkRule + +// serviceIDs contains service IDs sorted alphabetically. +var serviceIDs []string + +// initBlockedServices initializes package-level blocked service data. func initBlockedServices() { - serviceRules = make(map[string][]*rules.NetworkRule) - for _, s := range serviceRulesArray { - netRules := []*rules.NetworkRule{} + l := len(servicesData) + serviceIDs = make([]string, l) + serviceRules = make(map[string][]*rules.NetworkRule, l) + + for i, s := range servicesData { + netRules := make([]*rules.NetworkRule, 0, len(s.rules)) for _, text := range s.rules { rule, err := rules.NewNetworkRule(text, BlockedSvcsListID) if err != nil { - log.Error("rules.NewNetworkRule: %s rule: %s", err, text) + log.Error("parsing blocked service %q rule %q: %s", s.name, text, err) + continue } + netRules = append(netRules, rule) } + + serviceIDs[i] = s.name serviceRules[s.name] = netRules } + + slices.Sort(serviceIDs) + + log.Debug("filtering: initialized %d services", l) } // BlockedSvcKnown - return TRUE if a blocked service name is known @@ -289,6 +439,16 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string, global } } +func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(serviceIDs) + if err != nil { + aghhttp.Error(r, w, http.StatusInternalServerError, "encoding available services: %s", err) + + return + } +} + func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { d.confLock.RLock() list := d.Config.BlockedServices @@ -297,7 +457,7 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(list) if err != nil { - aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err) + aghhttp.Error(r, w, http.StatusInternalServerError, "encoding services: %s", err) return } @@ -323,6 +483,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ // registerBlockedServicesHandlers - register HTTP handlers func (d *DNSFilter) registerBlockedServicesHandlers() { + d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices) d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList) d.Config.HTTPRegister(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet) } 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 9bedeef7..4d438dfe 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:"-"` @@ -296,9 +296,11 @@ func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) { return clone } -// SetFilters - set new filters (synchronously or asynchronously) -// When filters are set asynchronously, the old filters continue working until the new filters are ready. -// In this case the caller must ensure that the old filter files are intact. +// SetFilters sets new filters, synchronously or asynchronously. When filters +// are set asynchronously, the old filters continue working until the new +// filters are ready. +// +// In this case the caller must ensure that the old filter files are intact. func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error { if async { params := filtersInitializerParams{ @@ -471,7 +473,7 @@ func (d *DNSFilter) matchSysHosts( return res, nil } - dnsres, _ := d.EtcHosts.MatchRequest(urlfilter.DNSRequest{ + dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{ Hostname: host, SortedClientTags: setts.ClientTags, // TODO(e.burkov): Wait for urlfilter update to pass net.IP. @@ -802,7 +804,7 @@ func (d *DNSFilter) matchHost( return Result{}, nil } - ureq := urlfilter.DNSRequest{ + ureq := &urlfilter.DNSRequest{ Hostname: host, SortedClientTags: setts.ClientTags, // TODO(e.burkov): Wait for urlfilter update to pass net.IP. 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/rewrites.go b/internal/filtering/rewrites.go index dab4c034..c1557158 100644 --- a/internal/filtering/rewrites.go +++ b/internal/filtering/rewrites.go @@ -130,10 +130,9 @@ func matchDomainWildcard(host, wildcard string) (ok bool) { // // The sorting priority: // -// A and AAAA > CNAME -// wildcard > exact -// lower level wildcard > higher level wildcard -// +// 1. A and AAAA > CNAME +// 2. wildcard > exact +// 3. lower level wildcard > higher level wildcard type rewritesSorted []*LegacyRewrite // Len implements the sort.Interface interface for legacyRewritesSorted. 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 f539adbe..e50b7904 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -2,10 +2,9 @@ package home import ( "bytes" + "encoding" "fmt" "net" - "os/exec" - "runtime" "sort" "strings" "sync" @@ -56,12 +55,49 @@ type clientSource uint // Client sources. The order determines the priority. const ( ClientSourceWHOIS clientSource = iota - ClientSourceRDNS ClientSourceARP + ClientSourceRDNS ClientSourceDHCP 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 { + WHOIS bool `yaml:"whois"` + ARP bool `yaml:"arp"` + RDNS bool `yaml:"rdns"` + DHCP bool `yaml:"dhcp"` + HostsFile bool `yaml:"hosts"` +} + // RuntimeClient information type RuntimeClient struct { WHOISInfo *RuntimeClientWHOISInfo @@ -99,6 +135,9 @@ type clientsContainer struct { // hosts database. etcHosts *aghnet.HostsContainer + // arpdb stores the neighbors retrieved from ARP. + arpdb aghnet.ARPDB + testing bool // if TRUE, this object is used for internal tests } @@ -109,6 +148,7 @@ func (clients *clientsContainer) Init( objects []*clientObject, dhcpServer *dhcpd.Server, etcHosts *aghnet.HostsContainer, + arpdb aghnet.ARPDB, ) { if clients.list != nil { log.Fatal("clients.list != nil") @@ -121,6 +161,7 @@ func (clients *clientsContainer) Init( clients.dhcpServer = dhcpServer clients.etcHosts = etcHosts + clients.arpdb = arpdb clients.addFromConfig(objects) if clients.testing { @@ -132,14 +173,14 @@ func (clients *clientsContainer) Init( clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged) } - go clients.handleHostsUpdates() + if clients.etcHosts != nil { + go clients.handleHostsUpdates() + } } func (clients *clientsContainer) handleHostsUpdates() { - if clients.etcHosts != nil { - for upd := range clients.etcHosts.Upd() { - clients.addFromHostsFile(upd) - } + for upd := range clients.etcHosts.Upd() { + clients.addFromHostsFile(upd) } } @@ -156,7 +197,9 @@ func (clients *clientsContainer) Start() { // Reload reloads runtime clients. func (clients *clientsContainer) Reload() { - clients.addFromSystemARP() + if clients.arpdb != nil { + clients.addFromSystemARP() + } } type clientObject struct { @@ -255,6 +298,8 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) { } func (clients *clientsContainer) periodicUpdate() { + defer log.OnPanic("clients container") + for { clients.Reload() time.Sleep(clientsUpdatePeriod) @@ -380,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 } @@ -476,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 @@ -530,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) } @@ -721,20 +767,18 @@ func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSourc clients.lock.Lock() defer clients.lock.Unlock() - ok = clients.addHostLocked(ip, host, src) - - return ok, nil + return clients.addHostLocked(ip, host, src), nil } // 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 } + rc.Host = host rc.Source = src } else { rc = &RuntimeClient{ @@ -754,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) @@ -782,41 +826,38 @@ 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 // command. func (clients *clientsContainer) addFromSystemARP() { - if runtime.GOOS == "windows" { + if err := clients.arpdb.Refresh(); err != nil { + log.Error("refreshing arp container: %s", err) + + clients.arpdb = aghnet.EmptyARPDB{} + return } - cmd := exec.Command("arp", "-a") - log.Tracef("executing %q %q", cmd.Path, cmd.Args) - data, err := cmd.Output() - if err != nil || cmd.ProcessState.ExitCode() != 0 { - log.Debug("command %q has failed: %q code:%d", - cmd.Path, err, cmd.ProcessState.ExitCode()) + ns := clients.arpdb.Neighbors() + if len(ns) == 0 { + log.Debug("refreshing arp container: the update is empty") + return } @@ -825,36 +866,20 @@ func (clients *clientsContainer) addFromSystemARP() { clients.rmHostsBySrc(ClientSourceARP) - n := 0 - // TODO(a.garipov): Rewrite to use bufio.Scanner. - lines := strings.Split(string(data), "\n") - for _, ln := range lines { - lparen := strings.Index(ln, " (") - rparen := strings.Index(ln, ") ") - if lparen == -1 || rparen == -1 || lparen >= rparen { - continue - } - - host := ln[:lparen] - ipStr := ln[lparen+2 : rparen] - ip := net.ParseIP(ipStr) - if netutil.ValidateDomainName(host) != nil || ip == nil { - continue - } - - ok := clients.addHostLocked(ip, host, ClientSourceARP) - if ok { - n++ + added := 0 + for _, n := range ns { + if clients.addHostLocked(n.IP, n.Name, ClientSourceARP) { + added++ } } - log.Debug("clients: added %d client aliases from 'arp -a' command output", n) + log.Debug("clients: added %d client aliases from arp neighborhood", added) } // updateFromDHCP adds the clients that have a non-empty hostname from the DHCP // server. func (clients *clientsContainer) updateFromDHCP(add bool) { - if clients.dhcpServer == nil { + if clients.dhcpServer == nil || !config.Clients.Sources.DHCP { return } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 6cc5d46f..8afd5621 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -18,7 +18,7 @@ func TestClients(t *testing.T) { clients := clientsContainer{} clients.testing = true - clients.Init(nil, nil, nil) + clients.Init(nil, nil, nil, nil) t.Run("add_success", func(t *testing.T) { c := &Client{ @@ -194,7 +194,7 @@ func TestClientsWHOIS(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil) + clients.Init(nil, nil, nil, nil) whois := &RuntimeClientWHOISInfo{ Country: "AU", Orgname: "Example Org", @@ -253,7 +253,7 @@ func TestClientsAddExisting(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil) + clients.Init(nil, nil, nil, nil) t.Run("simple", func(t *testing.T) { ip := net.IP{1, 1, 1, 1} @@ -332,7 +332,7 @@ func TestClientsCustomUpstream(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil) + clients.Init(nil, nil, nil, nil) // Add client with upstreams. ok, err := clients.Add(&Client{ 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 6ecb4ccb..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. @@ -51,6 +73,13 @@ type osConfig struct { RlimitNoFile uint64 `yaml:"rlimit_nofile"` } +type clientsConfig struct { + // Sources defines the set of sources to fetch the runtime clients from. + Sources *clientSourcesConf `yaml:"runtime_sources"` + // Persistent are the configured clients. + Persistent []*clientObject `yaml:"persistent"` +} + // configuration is loaded from YAML // field ordering is important -- yaml fields will mirror ordering from here type configuration struct { @@ -88,7 +117,7 @@ type configuration struct { // Clients contains the YAML representations of the persistent clients. // This field is only used for reading and writing persistent client data. // Keep this field sorted to ensure consistent ordering. - Clients []*clientObject `yaml:"clients"` + Clients *clientsConfig `yaml:"clients"` logSettings `yaml:",inline"` @@ -123,8 +152,9 @@ type dnsConfig struct { // UpstreamTimeout is the timeout for querying upstream servers. UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"` - // ResolveClients enables and disables resolving clients with RDNS. - ResolveClients bool `yaml:"resolve_clients"` + // PrivateNets is the set of IP networks for which the private reverse DNS + // resolver should be used. + PrivateNets []string `yaml:"private_networks"` // UsePrivateRDNS defines if the PTR requests for unknown addresses from // locally-served networks should be resolved via private PTR resolvers. @@ -179,6 +209,7 @@ var config = &configuration{ Ratelimit: 20, RefuseAny: true, AllServers: false, + HandleDDR: true, FastestTimeout: timeutil.Duration{ Duration: fastip.DefaultPingWaitTimeout, }, @@ -194,7 +225,6 @@ var config = &configuration{ FilteringEnabled: true, // whether or not use filter lists FiltersUpdateIntervalHours: 24, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, - ResolveClients: true, UsePrivateRDNS: true, }, TLS: tlsConfigSettings{ @@ -205,12 +235,21 @@ var config = &configuration{ DHCP: &dhcpd.ServerConfig{ LocalDomainName: "lan", }, + Clients: &clientsConfig{ + Sources: &clientSourcesConf{ + WHOIS: true, + ARP: true, + RDNS: true, + DHCP: true, + HostsFile: true, + }, + }, 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, @@ -285,25 +324,28 @@ func parseConfig() (err error) { return err } - uc := aghalg.UniqChecker{} - addPorts( - uc, - config.BindPort, - config.BetaBindPort, - 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, - config.TLS.PortHTTPS, - config.TLS.PortDNSOverTLS, - config.TLS.PortDNSOverQUIC, - config.TLS.PortDNSCrypt, + tcpPorts, + tcpPort(config.TLS.PortHTTPS), + tcpPort(config.TLS.PortDNSOverTLS), + 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) { @@ -317,8 +359,14 @@ func parseConfig() (err error) { return nil } -// addPorts is a helper for ports validation. It skips zero ports. -func addPorts(uc aghalg.UniqChecker, ports ...int) { +// udpPort is the port number for UDP protocol. +type udpPort int + +// tcpPort is the port number for TCP protocol. +type tcpPort int + +// 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 { if p != 0 { uc.Add(p) @@ -340,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) @@ -380,9 +429,7 @@ func (c *configuration) write() error { s.WriteDiskConfig(&c) dns := &config.DNS dns.FilteringConfig = c - dns.LocalPTRResolvers, - dns.ResolveClients, - dns.UsePrivateRDNS = s.RDNSSettings() + dns.LocalPTRResolvers, config.Clients.Sources.RDNS, dns.UsePrivateRDNS = s.RDNSSettings() } if Context.dhcpServer != nil { @@ -391,22 +438,23 @@ func (c *configuration) write() error { config.DHCP = c } - config.Clients = Context.clients.forConfig() + 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 98cbf31a..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, config.BetaBindPort, 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, 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 79b9f37e..91164696 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -3,14 +3,15 @@ package home import ( "context" "encoding/json" + "fmt" "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" @@ -27,12 +28,16 @@ type temporaryError interface { // Get the latest available version from the Internet func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + resp := &versionResponse{} if Context.disableUpdate { - // w.Header().Set("Content-Type", "application/json") resp.Disabled = true - _ = json.NewEncoder(w).Encode(resp) - // TODO(e.burkov): Add error handling and deal with headers. + err := json.NewEncoder(w).Encode(resp) + if err != nil { + aghhttp.Error(r, w, http.StatusInternalServerError, "writing body: %s", err) + } + return } @@ -44,30 +49,48 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { if r.ContentLength != 0 { err = json.NewDecoder(r.Body).Decode(req) if err != nil { - aghhttp.Error(r, w, http.StatusBadRequest, "JSON parse: %s", err) + aghhttp.Error(r, w, http.StatusBadRequest, "parsing request: %s", err) return } } + err = requestVersionInfo(resp, req.Recheck) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + aghhttp.Error(r, w, http.StatusBadGateway, "%s", err) + + return + } + + err = resp.setAllowedToAutoUpdate() + if err != nil { + // Don't wrap the error, because it's informative enough as is. + aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) + + return + } + + err = json.NewEncoder(w).Encode(resp) + if err != nil { + aghhttp.Error(r, w, http.StatusInternalServerError, "writing body: %s", err) + } +} + +// requestVersionInfo sets the VersionInfo field of resp if it can reach the +// update server. +func requestVersionInfo(resp *versionResponse, recheck bool) (err error) { for i := 0; i != 3; i++ { - func() { - Context.controlLock.Lock() - defer Context.controlLock.Unlock() - - resp.VersionInfo, err = Context.updater.VersionInfo(req.Recheck) - }() - + resp.VersionInfo, err = Context.updater.VersionInfo(recheck) if err != nil { var terr temporaryError if errors.As(err, &terr) && terr.Temporary() { - // Temporary network error. This case may happen while - // we're restarting our DNS server. Log and sleep for - // some time. + // Temporary network error. This case may happen while we're + // restarting our DNS server. Log and sleep for some time. // // See https://github.com/AdguardTeam/AdGuardHome/issues/934. d := time.Duration(i) * time.Second - log.Info("temp net error: %q; sleeping for %s and retrying", err, d) + log.Info("update: temp net error: %q; sleeping for %s and retrying", err, d) time.Sleep(d) continue @@ -76,29 +99,14 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { break } + if err != nil { vcu := Context.updater.VersionCheckURL() - // TODO(a.garipov): Figure out the purpose of %T verb. - aghhttp.Error( - r, - w, - http.StatusBadGateway, - "Couldn't get version check json from %s: %T %s\n", - vcu, - err, - err, - ) - return + return fmt.Errorf("getting version info from %s: %s", vcu, err) } - resp.confirmAutoUpdate() - - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(resp) - if err != nil { - aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err) - } + return nil } // handleUpdate performs an update to the latest available version procedure. @@ -109,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) @@ -121,85 +140,88 @@ 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. type versionResponse struct { - Disabled bool `json:"disabled"` updater.VersionInfo + Disabled bool `json:"disabled"` } -// confirmAutoUpdate checks the real possibility of auto update. -func (vr *versionResponse) confirmAutoUpdate() { - if vr.CanAutoUpdate != nil && *vr.CanAutoUpdate { - canUpdate := true - - var tlsConf *tlsConfigSettings - if runtime.GOOS != "windows" { - tlsConf = &tlsConfigSettings{} - Context.tls.WriteDiskConfig(tlsConf) - } - - if tlsConf != nil && - ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || - tlsConf.PortDNSOverTLS < 1024 || - tlsConf.PortDNSOverQUIC < 1024)) || - config.BindPort < 1024 || - config.DNS.Port < 1024) { - canUpdate, _ = aghnet.CanBindPrivilegedPorts() - } - vr.CanAutoUpdate = &canUpdate +// 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 != aghalg.NBTrue { + return nil } + + tlsConf := &tlsConfigSettings{} + Context.tls.WriteDiskConfig(tlsConf) + + canUpdate := true + if tlsConfUsesPrivilegedPorts(tlsConf) || config.BindPort < 1024 || config.DNS.Port < 1024 { + canUpdate, err = aghnet.CanBindPrivilegedPorts() + if err != nil { + return fmt.Errorf("checking ability to bind privileged ports: %w", err) + } + } + + vr.CanAutoUpdate = aghalg.BoolToNullBool(canUpdate) + + return nil +} + +// tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration +// indicates that privileged ports are used. +func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) { + return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024) } // 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 4c27abd9..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. @@ -25,7 +25,7 @@ const ( defaultPortDNS = 53 defaultPortHTTP = 80 defaultPortHTTPS = 443 - defaultPortQUIC = 784 + defaultPortQUIC = 853 defaultPortTLS = 853 ) @@ -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) @@ -77,13 +77,36 @@ func initDNSServer() (err error) { filterConf.HTTPRegister = httpRegister Context.dnsFilter = filtering.New(&filterConf, nil) + var privateNets netutil.SubnetSet + switch len(config.DNS.PrivateNets) { + case 0: + // Use an optimized locally-served matcher. + privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed) + case 1: + var n *net.IPNet + n, err = netutil.ParseSubnet(config.DNS.PrivateNets[0]) + if err != nil { + return fmt.Errorf("preparing the set of private subnets: %w", err) + } + + privateNets = n + default: + var nets []*net.IPNet + nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...) + if err != nil { + return fmt.Errorf("preparing the set of private subnets: %w", err) + } + + privateNets = netutil.SliceSubnetSet(nets) + } + p := dnsforward.DNSCreateParams{ - DNSFilter: Context.dnsFilter, - Stats: Context.stats, - QueryLog: Context.queryLog, - SubnetDetector: Context.subnetDetector, - Anonymizer: anonymizer, - LocalDomain: config.DHCP.LocalDomainName, + DNSFilter: Context.dnsFilter, + Stats: Context.stats, + QueryLog: Context.queryLog, + PrivateNets: privateNets, + Anonymizer: anonymizer, + LocalDomain: config.DHCP.LocalDomainName, } if Context.dhcpServer != nil { p.DHCPServer = Context.dhcpServer @@ -112,8 +135,13 @@ func initDNSServer() (err error) { return fmt.Errorf("dnsServer.Prepare: %w", err) } - Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS) - Context.whois = initWHOIS(&Context.clients) + if config.Clients.Sources.RDNS { + Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS) + } + + if config.Clients.Sources.WHOIS { + Context.whois = initWHOIS(&Context.clients) + } Context.filters.Init() return nil @@ -130,10 +158,11 @@ func onDNSRequest(pctx *proxy.DNSContext) { return } - if config.DNS.ResolveClients && !ip.IsLoopback() { + srcs := config.Clients.Sources + if srcs.RDNS && !ip.IsLoopback() { Context.rdns.Begin(ip) } - if !Context.subnetDetector.IsSpecialNetwork(ip) { + if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) { Context.whois.Begin(ip) } } @@ -192,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) } @@ -216,7 +249,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { newConf.FilterHandler = applyAdditionalFiltering newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams - newConf.ResolveClients = dnsConf.ResolveClients + newConf.ResolveClients = config.Clients.Sources.RDNS newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration @@ -301,24 +334,28 @@ func getDNSEncryption() (de dnsEncryption) { // applyAdditionalFiltering adds additional client information and settings if // the client has them. -func applyAdditionalFiltering(clientAddr net.IP, clientID string, setts *filtering.Settings) { +func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering.Settings) { Context.dnsFilter.ApplyBlockedServices(setts, nil, true) - if clientAddr == nil { + log.Debug("looking up settings for client with ip %s and clientid %q", clientIP, clientID) + + if clientIP == nil { return } - setts.ClientIP = clientAddr + setts.ClientIP = clientIP c, ok := Context.clients.Find(clientID) if !ok { - c, ok = Context.clients.Find(clientAddr.String()) + c, ok = Context.clients.Find(clientIP.String()) if !ok { + log.Debug("client with ip %s and clientid %q not found", clientIP, clientID) + return } } - log.Debug("using settings for client %s with ip %s and clientid %q", c.Name, clientAddr, clientID) + log.Debug("using settings for client %q with ip %s and clientid %q", c.Name, clientIP, clientID) if c.UseOwnBlockedServices { Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false) @@ -359,11 +396,16 @@ func startDNSServer() error { Context.queryLog.Start() const topClientsNumber = 100 // the number of clients to get - for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) { - if config.DNS.ResolveClients && !ip.IsLoopback() { + for _, ip := range Context.stats.TopClientsIP(topClientsNumber) { + if ip == nil { + continue + } + + srcs := config.Clients.Sources + if srcs.RDNS && !ip.IsLoopback() { Context.rdns.Begin(ip) } - if !Context.subnetDetector.IsSpecialNetwork(ip) { + if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) { Context.whois.Begin(ip) } } @@ -413,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 021751e8..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 @@ -66,8 +66,6 @@ type homeContext struct { updater *updater.Updater - subnetDetector *aghnet.SubnetDetector - // mux is our custom http.ServeMux. mux *http.ServeMux @@ -175,6 +173,11 @@ func setupContext(args options) { os.Exit(0) } + + if !args.noEtcHosts && config.Clients.Sources.HostsFile { + err = setupHostsContainer() + fatalOnError(err) + } } Context.mux = http.NewServeMux() @@ -287,33 +290,35 @@ func setupConfig(args options) (err error) { ConfName: config.getConfigFilename(), }) - if !args.noEtcHosts { - if err = setupHostsContainer(); err != nil { - return err - } + var arpdb aghnet.ARPDB + if config.Clients.Sources.ARP { + arpdb = aghnet.NewARPDB() } - Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts) + Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb) if args.bindPort != 0 { - uc := aghalg.UniqChecker{} - addPorts( - uc, - args.bindPort, - config.BetaBindPort, - 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, - config.TLS.PortHTTPS, - config.TLS.PortDNSOverTLS, - config.TLS.PortDNSOverQUIC, - config.TLS.PortDNSCrypt, + tcpPorts, + tcpPort(config.TLS.PortHTTPS), + tcpPort(config.TLS.PortDNSOverTLS), + 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 @@ -390,9 +395,6 @@ func run(args options, clientBuildFS fs.FS) { // configure log level and output configureLogger(args) - // Go memory hacks - memoryUsage(args) - // Print the first message after logger is configured. log.Println(version.Full()) log.Debug("current working directory is %s", Context.workDir) @@ -469,9 +471,6 @@ func run(args options, clientBuildFS fs.FS) { Context.web, err = initWeb(args, clientBuildFS) fatalOnError(err) - Context.subnetDetector, err = aghnet.NewSubnetDetector() - fatalOnError(err) - if !Context.firstRun { err = initDNSServer() fatalOnError(err) @@ -521,27 +520,15 @@ func StartMods() error { func checkPermissions() { log.Info("Checking if AdGuard Home has necessary permissions") - if runtime.GOOS == "windows" { - // On Windows we need to have admin rights to run properly - - admin, _ := aghos.HaveAdminRights() - if admin { - return - } - + if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil { log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.") } // We should check if AdGuard Home is able to bind to port 53 - ok, err := aghnet.CanBindPort(53) - - if ok { - log.Info("AdGuard Home can bind to port 53") - return - } - - if errors.Is(err, os.ErrPermission) { - msg := `Permission check failed. + err := aghnet.CheckPort("tcp", net.IP{127, 0, 0, 1}, defaultPortDNS) + if err != nil { + if errors.Is(err, os.ErrPermission) { + log.Fatal(`Permission check failed. AdGuard Home is not allowed to bind to privileged ports (for instance, port 53). Please note, that this is crucial for a server to be able to use privileged ports. @@ -549,16 +536,17 @@ Please note, that this is crucial for a server to be able to use privileged port You have two options: 1. Run AdGuard Home with root privileges 2. On Linux you can grant the CAP_NET_BIND_SERVICE capability: -https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser` +https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`) + } - log.Fatal(msg) + log.Info( + "AdGuard failed to bind to port 53: %s\n\n"+ + "Please note, that this is crucial for a DNS server to be able to use that port.", + err, + ) } - msg := fmt.Sprintf(`AdGuard failed to bind to port 53 due to %v - -Please note, that this is crucial for a DNS server to be able to use that port.`, err) - - log.Info(msg) + log.Info("AdGuard Home can bind to port 53") } // Write PID to a file @@ -614,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 { @@ -635,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) @@ -665,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/memory.go b/internal/home/memory.go deleted file mode 100644 index bcb6e70f..00000000 --- a/internal/home/memory.go +++ /dev/null @@ -1,40 +0,0 @@ -package home - -import ( - "os" - "runtime/debug" - "time" - - "github.com/AdguardTeam/golibs/log" -) - -// memoryUsage implements a couple of not really beautiful hacks which purpose is to -// make OS reclaim the memory freed by AdGuard Home as soon as possible. -// See this for the details on the performance hits & gains: -// https://github.com/AdguardTeam/AdGuardHome/internal/issues/2044#issuecomment-687042211 -func memoryUsage(args options) { - if args.disableMemoryOptimization { - log.Info("Memory optimization is disabled") - return - } - - // Makes Go allocate heap at a slower pace - // By default we keep it at 50% - debug.SetGCPercent(50) - - // madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED - // instead of MADV_FREE on Linux when returning memory to the - // kernel. This is less efficient, but causes RSS numbers to drop - // more quickly. - _ = os.Setenv("GODEBUG", "madvdontneed=1") - - // periodically call "debug.FreeOSMemory" so - // that the OS could reclaim the free memory - go func() { - ticker := time.NewTicker(5 * time.Minute) - for range ticker.C { - log.Debug("free os memory") - debug.FreeOSMemory() - } - }() -} 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/options.go b/internal/home/options.go index 6d9a1f99..6f5a4d8d 100644 --- a/internal/home/options.go +++ b/internal/home/options.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/AdguardTeam/AdGuardHome/internal/version" + "github.com/AdguardTeam/golibs/log" ) // options passed from command-line arguments @@ -27,10 +28,6 @@ type options struct { // runningAsService flag is set to true when options are passed from the service runner runningAsService bool - // disableMemoryOptimization - disables memory optimization hacks - // see memoryUsage() function for the details - disableMemoryOptimization bool - glinetMode bool // Activate GL-Inet compatibility mode // noEtcHosts flag should be provided when /etc/hosts file shouldn't be @@ -178,10 +175,14 @@ var noCheckUpdateArg = arg{ } var disableMemoryOptimizationArg = arg{ - "Disable memory optimization.", + "Deprecated. Disable memory optimization.", "no-mem-optimization", "", - nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil, - func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) }, + nil, nil, func(_ options, _ string) (f effect, err error) { + log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated") + + return nil, nil + }, + func(o options) []string { return nil }, } var verboseArg = arg{ @@ -229,13 +230,19 @@ var helpArg = arg{ } var noEtcHostsArg = arg{ - description: "Do not use the OS-provided hosts.", + description: "Deprecated. Do not use the OS-provided hosts.", longName: "no-etc-hosts", shortName: "", updateWithValue: nil, updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil }, - effect: nil, - serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) }, + effect: func(_ options, _ string) (f effect, err error) { + log.Info( + "warning: --no-etc-hosts flag is deprecated and will be removed in the future versions", + ) + + return nil, nil + }, + serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) }, } var localFrontendArg = arg{ diff --git a/internal/home/options_test.go b/internal/home/options_test.go index b189f045..21972b0a 100644 --- a/internal/home/options_test.go +++ b/internal/home/options_test.go @@ -101,9 +101,13 @@ func TestParseDisableUpdate(t *testing.T) { assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update") } +// TODO(e.burkov): Remove after v0.108.0. func TestParseDisableMemoryOptimization(t *testing.T) { - assert.False(t, testParseOK(t).disableMemoryOptimization, "empty is not disable update") - assert.True(t, testParseOK(t, "--no-mem-optimization").disableMemoryOptimization, "--no-mem-optimization is disable update") + o, eff, err := parse("", []string{"--no-mem-optimization"}) + require.NoError(t, err) + + assert.Nil(t, eff) + assert.Zero(t, o) } func TestParseService(t *testing.T) { @@ -127,8 +131,6 @@ func TestParseUnknown(t *testing.T) { } func TestSerialize(t *testing.T) { - const reportFmt = "expected %s but got %s" - testCases := []struct { name string opts options @@ -173,19 +175,14 @@ func TestSerialize(t *testing.T) { name: "glinet_mode", opts: options{glinetMode: true}, ss: []string{"--glinet"}, - }, { - name: "disable_mem_opt", - opts: options{disableMemoryOptimization: true}, - ss: []string{"--no-mem-optimization"}, }, { name: "multiple", opts: options{ - serviceControlAction: "run", - configFilename: "config", - workDir: "work", - pidFile: "pid", - disableUpdate: true, - disableMemoryOptimization: true, + serviceControlAction: "run", + configFilename: "config", + workDir: "work", + pidFile: "pid", + disableUpdate: true, }, ss: []string{ "-c", "config", @@ -193,18 +190,13 @@ func TestSerialize(t *testing.T) { "-s", "run", "--pidfile", "pid", "--no-check-update", - "--no-mem-optimization", }, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := serialize(tc.opts) - require.Lenf(t, result, len(tc.ss), reportFmt, tc.ss, result) - - for i, r := range result { - assert.Equalf(t, tc.ss[i], r, reportFmt, tc.ss, result) - } + assert.ElementsMatch(t, tc.ss, result) }) } } diff --git a/internal/home/rdns.go b/internal/home/rdns.go index cba748af..924aff37 100644 --- a/internal/home/rdns.go +++ b/internal/home/rdns.go @@ -16,18 +16,17 @@ type RDNS struct { exchanger dnsforward.RDNSExchanger clients *clientsContainer - // usePrivate is used to store the state of current private RDNS - // resolving settings and to react to it's changes. + // usePrivate is used to store the state of current private RDNS resolving + // settings and to react to it's changes. usePrivate uint32 // ipCh used to pass client's IP to rDNS workerLoop. ipCh chan net.IP // ipCache caches the IP addresses to be resolved by rDNS. The resolved - // address stays here while it's inside clients. After leaving clients - // the address will be resolved once again. If the address couldn't be - // resolved, cache prevents further attempts to resolve it for some - // time. + // address stays here while it's inside clients. After leaving clients the + // address will be resolved once again. If the address couldn't be + // resolved, cache prevents further attempts to resolve it for some time. ipCache cache.Cache } @@ -125,14 +124,12 @@ func (r *RDNS) workerLoop() { log.Debug("rdns: resolving %q: %s", ip, err) continue - } - - if host == "" { + } else if host == "" { continue } - // Don't handle any errors since AddHost doesn't return non-nil - // errors for now. + // Don't handle any errors since AddHost doesn't return non-nil errors + // for now. _, _ = r.clients.AddHost(ip, host, ClientSourceRDNS) } } 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 e5d0ba39..831a80d0 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -4,6 +4,7 @@ import ( "fmt" "io/fs" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -82,10 +83,15 @@ func svcStatus(s service.Service) (status service.Status, err error) { // On OpenWrt, the service utility may not exist. We use our service script // directly in this case. func svcAction(s service.Service, action string) (err error) { - if runtime.GOOS == "darwin" && - action == "start" && - !strings.HasPrefix(Context.workDir, "/Applications/") { - log.Info("warning: service must be started from within the /Applications directory") + if runtime.GOOS == "darwin" && action == "start" { + var exe string + if exe, err = os.Executable(); err != nil { + log.Error("starting service: getting executable path: %s", err) + } else if exe, err = filepath.EvalSymlinks(exe); err != nil { + log.Error("starting service: evaluating executable symlinks: %s", err) + } else if !strings.HasPrefix(exe, "/Applications/") { + log.Info("warning: service must be started from within the /Applications directory") + } } err = service.Control(s, action) @@ -427,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 @@ -459,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 @@ -579,6 +588,9 @@ status() { } ` +// freeBSDScript is the source of the daemon script for FreeBSD. Keep as close +// as possible to the https://github.com/kardianos/service/blob/18c957a3dc1120a2efe77beb401d476bade9e577/service_freebsd.go#L204. +// // TODO(a.garipov): Don't use .WorkingDirectory here. There are currently no // guarantees that it will actually be the required directory. // @@ -587,23 +599,26 @@ const freeBSDScript = `#!/bin/sh # PROVIDE: {{.Name}} # REQUIRE: networking # KEYWORD: shutdown + . /etc/rc.subr + name="{{.Name}}" {{.Name}}_env="IS_DAEMON=1" {{.Name}}_user="root" pidfile_child="/var/run/${name}.pid" pidfile="/var/run/${name}_daemon.pid" command="/usr/sbin/daemon" -command_args="-P ${pidfile} -p ${pidfile_child} -f -r {{.WorkingDirectory}}/{{.Name}}" +command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirectory}}/{{.Name}}" run_rc_command "$1" ` -const openBSDScript = `#!/bin/sh +const openBSDScript = `#!/bin/ksh # # $OpenBSD: {{ .SvcInfo }} daemon="{{.Path}}" daemon_flags={{ .Arguments | args }} +daemon_logger="daemon.info" . /etc/rc.d/rc.subr 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 679a7437..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, " ") + `"` }, @@ -314,12 +314,13 @@ func (s *openbsdRunComService) runCom(cmd string) (out string, err error) { // TODO(e.burkov): It's possible that os.ErrNotExist is caused by // something different than the service script's non-existence. Keep it // in mind, when replace the aghos.RunCommand. - _, out, err = aghos.RunCommand(scriptPath, cmd) + var outData []byte + _, outData, err = aghos.RunCommand(scriptPath, cmd) if errors.Is(err, os.ErrNotExist) { return "", service.ErrNotInstalled } - return out, err + return string(outData), err } // Status implements service.Service interface for *openbsdRunComService. @@ -389,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 eeffe3b6..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, - config.BindPort, - config.BetaBindPort, - config.DNS.Port, - setts.PortHTTPS, - setts.PortDNSOverTLS, - setts.PortDNSOverQUIC, - setts.PortDNSCrypt, + err = validatePorts( + tcpPort(config.BindPort), + tcpPort(config.BetaBindPort), + tcpPort(setts.PortHTTPS), + tcpPort(setts.PortDNSOverTLS), + 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, - config.BindPort, - config.BetaBindPort, - config.DNS.Port, - data.PortHTTPS, - data.PortDNSOverTLS, - data.PortDNSOverQUIC, - data.PortDNSCrypt, + err = validatePorts( + tcpPort(config.BindPort), + tcpPort(config.BetaBindPort), + tcpPort(data.PortHTTPS), + tcpPort(data.PortDNSOverTLS), + 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 34297470..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,17 +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 = 13 +const currentSchemaVersion = 14 // These aliases are provided for convenience. type ( - any = interface{} yarr = []any - yobj = map[any]any + yobj = map[string]any ) // Performs necessary upgrade operations if needed @@ -86,6 +86,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { upgradeSchema10to11, upgradeSchema11to12, upgradeSchema12to13, + upgradeSchema13to14, } n := 0 @@ -104,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 @@ -173,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 @@ -213,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: @@ -304,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 { @@ -323,7 +328,7 @@ func upgradeSchema5to6(diskConf yobj) error { } } - var macVal interface{} + var macVal any macVal, ok = c["mac"] if ok { var mac string @@ -374,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 { @@ -726,7 +731,7 @@ func upgradeSchema12to13(diskConf yobj) (err error) { var dhcp yobj dhcp, ok = dhcpVal.(yobj) if !ok { - return fmt.Errorf("unexpected type of dhcp: %T", dnsVal) + return fmt.Errorf("unexpected type of dhcp: %T", dhcpVal) } const field = "local_domain_name" @@ -737,6 +742,68 @@ func upgradeSchema12to13(diskConf yobj) (err error) { return nil } +// upgradeSchema13to14 performs the following changes: +// +// # BEFORE: +// 'clients': +// - 'name': 'client-name' +// # … +// +// # AFTER: +// 'clients': +// 'persistent': +// - 'name': 'client-name' +// # … +// 'runtime_sources': +// 'whois': true +// 'arp': true +// 'rdns': true +// 'dhcp': true +// 'hosts': true +// +func upgradeSchema13to14(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 13 to 14") + diskConf["schema_version"] = 14 + + clientsVal, ok := diskConf["clients"] + if !ok { + clientsVal = yarr{} + } + + var rdnsSrc bool + if dnsVal, dok := diskConf["dns"]; dok { + var dnsSettings yobj + dnsSettings, ok = dnsVal.(yobj) + if !ok { + return fmt.Errorf("unexpected type of dns: %T", dnsVal) + } + + var rdnsSrcVal any + rdnsSrcVal, ok = dnsSettings["resolve_clients"] + if ok { + rdnsSrc, ok = rdnsSrcVal.(bool) + if !ok { + return fmt.Errorf("unexpected type of resolve_clients: %T", rdnsSrcVal) + } + + delete(dnsSettings, "resolve_clients") + } + } + + diskConf["clients"] = yobj{ + "persistent": clientsVal, + "runtime_sources": &clientSourcesConf{ + WHOIS: true, + ARP: true, + RDNS: rdnsSrc, + DHCP: true, + HostsFile: true, + }, + } + + return nil +} + // TODO(a.garipov): Replace with log.Output when we port it to our logging // package. func funcName() string { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index c63bc443..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) @@ -513,46 +513,129 @@ func TestUpgradeSchema11to12(t *testing.T) { } func TestUpgradeSchema12to13(t *testing.T) { - t.Run("no_dns", func(t *testing.T) { - conf := yobj{} + const newSchemaVer = 13 - err := upgradeSchema12to13(conf) - require.NoError(t, err) - - assert.Equal(t, conf["schema_version"], 13) - }) - - t.Run("no_dhcp", func(t *testing.T) { - conf := yobj{ - "dns": yobj{}, - } - - err := upgradeSchema12to13(conf) - require.NoError(t, err) - - assert.Equal(t, conf["schema_version"], 13) - }) - - t.Run("good", func(t *testing.T) { - conf := yobj{ + testCases := []struct { + in yobj + want yobj + name string + }{{ + in: yobj{}, + want: yobj{"schema_version": newSchemaVer}, + name: "no_dns", + }, { + in: yobj{"dns": yobj{}}, + want: yobj{ + "dns": yobj{}, + "schema_version": newSchemaVer, + }, + name: "no_dhcp", + }, { + in: yobj{ "dns": yobj{ "local_domain_name": "lan", }, "dhcp": yobj{}, - "schema_version": 12, - } - - wantConf := yobj{ + "schema_version": newSchemaVer - 1, + }, + want: yobj{ "dns": yobj{}, "dhcp": yobj{ "local_domain_name": "lan", }, - "schema_version": 13, - } + "schema_version": newSchemaVer, + }, + name: "good", + }} - err := upgradeSchema12to13(conf) - require.NoError(t, err) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := upgradeSchema12to13(tc.in) + require.NoError(t, err) - assert.Equal(t, wantConf, conf) - }) + assert.Equal(t, tc.want, tc.in) + }) + } +} + +func TestUpgradeSchema13to14(t *testing.T) { + const newSchemaVer = 14 + + testClient := &clientObject{ + Name: "agh-client", + IDs: []string{"id1"}, + UseGlobalSettings: true, + } + + testCases := []struct { + in yobj + want yobj + name string + }{{ + in: yobj{}, + want: yobj{ + "schema_version": newSchemaVer, + // The clients field will be added anyway. + "clients": yobj{ + "persistent": yarr{}, + "runtime_sources": &clientSourcesConf{ + WHOIS: true, + ARP: true, + RDNS: false, + DHCP: true, + HostsFile: true, + }, + }, + }, + name: "no_clients", + }, { + in: yobj{ + "clients": []*clientObject{testClient}, + }, + want: yobj{ + "schema_version": newSchemaVer, + "clients": yobj{ + "persistent": []*clientObject{testClient}, + "runtime_sources": &clientSourcesConf{ + WHOIS: true, + ARP: true, + RDNS: false, + DHCP: true, + HostsFile: true, + }, + }, + }, + name: "no_dns", + }, { + in: yobj{ + "clients": []*clientObject{testClient}, + "dns": yobj{ + "resolve_clients": true, + }, + }, + want: yobj{ + "schema_version": newSchemaVer, + "clients": yobj{ + "persistent": []*clientObject{testClient}, + "runtime_sources": &clientSourcesConf{ + WHOIS: true, + ARP: true, + RDNS: true, + DHCP: true, + HostsFile: true, + }, + }, + "dns": yobj{}, + }, + name: "good", + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := upgradeSchema13to14(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } } diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index a9810629..b4cfd7e5 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -14,7 +14,7 @@ import ( "github.com/miekg/dns" ) -type logEntryHandler (func(t json.Token, ent *logEntry) error) +type logEntryHandler func(t json.Token, ent *logEntry) error var logEntryHandlers = map[string]logEntryHandler{ "CID": func(t json.Token, ent *logEntry) error { @@ -109,6 +109,16 @@ var logEntryHandlers = map[string]logEntryHandler{ return err }, + "ECS": func(t json.Token, ent *logEntry) error { + v, ok := t.(string) + if !ok { + return nil + } + + ent.ReqECS = v + + return nil + }, "Cached": func(t json.Token, ent *logEntry) error { v, ok := t.(bool) if !ok { diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 3d651c81..c5d45280 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -32,6 +32,7 @@ func TestDecodeLogEntry(t *testing.T) { `"QT":"A",` + `"QC":"IN",` + `"CP":"",` + + `"ECS":"1.2.3.0/24",` + `"Answer":"` + ansStr + `",` + `"Cached":true,` + `"AD":true,` + @@ -58,6 +59,7 @@ func TestDecodeLogEntry(t *testing.T) { QClass: "IN", ClientID: "cli42", ClientProto: "", + ReqECS: "1.2.3.0/24", Answer: ans, Cached: true, Result: filtering.Result{ 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 e4bb63aa..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) { @@ -78,6 +78,10 @@ func (l *queryLog) entryToJSON(entry *logEntry, anonFunc aghnet.IPMutFunc) (json jsonEntry["client_id"] = entry.ClientID } + if entry.ReqECS != "" { + jsonEntry["ecs"] = entry.ReqECS + } + if len(entry.Result.Rules) > 0 { if r := entry.Result.Rules[0]; len(r.Text) > 0 { jsonEntry["rule"] = r.Text diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 7dc38824..24eec40e 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -81,6 +81,8 @@ type logEntry struct { QType string `json:"QT"` QClass string `json:"QC"` + ReqECS string `json:"ECS,omitempty"` + ClientID string `json:"CID,omitempty"` ClientProto ClientProto `json:"CP"` @@ -147,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) { @@ -189,6 +191,10 @@ func (l *queryLog) Add(params *AddParams) { AuthenticatedData: params.AuthenticatedData, } + if params.ReqECS != nil { + entry.ReqECS = params.ReqECS.String() + } + if params.Answer != nil { var a []byte a, err = params.Answer.Pack() 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 18b52938..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,15 +71,16 @@ 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. type AddParams struct { Question *dns.Msg + // ReqECS is the IP network extracted from EDNS Client-Subnet option of a + // request. + ReqECS *net.IPNet + // Answer is the response which is sent to the client, if any. Answer *dns.Msg @@ -87,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/querylog/searchcriterion.go b/internal/querylog/searchcriterion.go index 0595fd6e..a9bd4cff 100644 --- a/internal/querylog/searchcriterion.go +++ b/internal/querylog/searchcriterion.go @@ -99,24 +99,10 @@ func (c *searchCriterion) quickMatch(line string, findClient quickMatchClientFun } if c.strict { - return ctDomainOrClientCaseStrict( - c.value, - c.asciiVal, - clientID, - name, - host, - ip, - ) + return ctDomainOrClientCaseStrict(c.value, c.asciiVal, clientID, name, host, ip) } - return ctDomainOrClientCaseNonStrict( - c.value, - c.asciiVal, - clientID, - name, - host, - ip, - ) + return ctDomainOrClientCaseNonStrict(c.value, c.asciiVal, clientID, name, host, ip) case ctFilteringStatus: // Go on, as we currently don't do quick matches against // filtering statuses. 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 98ef459a..74969289 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -1,32 +1,32 @@ module github.com/AdguardTeam/AdGuardHome/internal/tools -go 1.17 +go 1.18 require ( - github.com/fzipp/gocyclo v0.4.0 + 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.9.5 - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 - golang.org/x/tools v0.1.8 - honnef.co/go/tools v0.2.2 - mvdan.cc/gofumpt v0.2.1 - mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 + 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-20220706161116-678bad134442 ) require ( - github.com/BurntSushi/toml v0.4.1 // indirect + github.com/BurntSushi/toml v1.2.0 // indirect github.com/client9/misspell v0.3.4 // indirect - github.com/google/go-cmp v0.5.6 // 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/mod v0.5.1 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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 e1b2e226..144cd0eb 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -34,8 +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 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/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= @@ -77,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= @@ -92,14 +91,12 @@ github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 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.4.0 h1:IykTnjwh2YLyYkGa0y92iTTEQcnyAz0r9zOo15EbJ7k= -github.com/fzipp/gocyclo v0.4.0/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,8 +154,8 @@ 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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -168,6 +165,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -177,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= @@ -200,6 +198,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -219,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= @@ -241,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= @@ -276,7 +273,6 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -284,18 +280,20 @@ github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2f github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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 v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +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 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +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= @@ -319,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.9.5 h1:Wiyf78NNedu8RClwW0vPRgPKCY7LJX4WujjJcPV2Nwg= -github.com/securego/gosec/v2 v2.9.5/go.mod h1:lG831xFHrZofatyJb9Y5yMUE8Ws6z5U5CMHe9vYn1kM= +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= @@ -351,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= @@ -401,7 +397,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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= @@ -413,6 +410,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +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= @@ -425,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= @@ -436,8 +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 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +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= @@ -473,11 +472,13 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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= @@ -494,6 +495,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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/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= @@ -539,17 +542,18 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210119212857-b64e53b001e4/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= 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-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-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= @@ -606,21 +610,20 @@ 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= golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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= @@ -714,7 +717,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -728,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= @@ -737,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.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= -honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs= -mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig= -mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 h1:Jh3LAeMt1eGpxomyu3jVkmVZWW2MxZ1qIIV2TZ/nRio= -mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5/go.mod h1:b8RRCBm0eeiWR8cfN88xeq2G5SG3VKGO+5UPWi5FSOY= +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-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 edf046af..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 { - NewVersion string `json:"new_version,omitempty"` - Announcement string `json:"announcement,omitempty"` - AnnouncementURL string `json:"announcement_url,omitempty"` - SelfUpdateMinVersion string `json:"-"` - CanAutoUpdate *bool `json:"can_autoupdate,omitempty"` + 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/agh/agh.go b/internal/v1/agh/agh.go new file mode 100644 index 00000000..212da4d6 --- /dev/null +++ b/internal/v1/agh/agh.go @@ -0,0 +1,33 @@ +// Package agh contains common entities and interfaces of AdGuard Home. +// +// TODO(a.garipov): Move to the upper-level internal/. +package agh + +import "context" + +// Service is the interface for API servers. +// +// TODO(a.garipov): Consider adding a context to Start. +// +// TODO(a.garipov): Consider adding a Wait method or making an extension +// interface for that. +type Service interface { + // Start starts the service. It does not block. + Start() (err error) + + // Shutdown gracefully stops the service. ctx is used to determine + // a timeout before trying to stop the service less gracefully. + Shutdown(ctx context.Context) (err error) +} + +// type check +var _ Service = EmptyService{} + +// EmptyService is a Service that does nothing. +type EmptyService struct{} + +// Start implements the Service interface for EmptyService. +func (EmptyService) Start() (err error) { return nil } + +// Shutdown implements the Service interface for EmptyService. +func (EmptyService) Shutdown(_ context.Context) (err error) { return nil } diff --git a/internal/v1/cmd/cmd.go b/internal/v1/cmd/cmd.go new file mode 100644 index 00000000..2f61509b --- /dev/null +++ b/internal/v1/cmd/cmd.go @@ -0,0 +1,67 @@ +// Package cmd is the AdGuard Home entry point. It contains the on-disk +// configuration file utilities, signal processing logic, and so on. +// +// TODO(a.garipov): Move to the upper-level internal/. +package cmd + +import ( + "context" + "io/fs" + "math/rand" + "net/netip" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/v1/websvc" + "github.com/AdguardTeam/golibs/log" +) + +// Main is the entry point of application. +func Main(clientBuildFS fs.FS) { + // # Initial Configuration + + start := time.Now() + rand.Seed(start.UnixNano()) + + // TODO(a.garipov): Set up logging. + + // # Web Service + + // TODO(a.garipov): Use in the Web service. + _ = clientBuildFS + + // TODO(a.garipov): Make configurable. + web := websvc.New(&websvc.Config{ + Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:3001")}, + Start: start, + Timeout: 60 * time.Second, + }) + + err := web.Start() + fatalOnError(err) + + sigHdlr := newSignalHandler( + web, + ) + + go sigHdlr.handle() + + select {} +} + +// defaultTimeout is the timeout used for some operations where another timeout +// hasn't been defined yet. +const defaultTimeout = 15 * time.Second + +// ctxWithDefaultTimeout is a helper function that returns a context with +// timeout set to defaultTimeout. +func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) { + return context.WithTimeout(context.Background(), defaultTimeout) +} + +// fatalOnError is a helper that exits the program with an error code if err is +// not nil. It must only be used within Main. +func fatalOnError(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/internal/v1/cmd/signal.go b/internal/v1/cmd/signal.go new file mode 100644 index 00000000..b66075f6 --- /dev/null +++ b/internal/v1/cmd/signal.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "os" + + "github.com/AdguardTeam/AdGuardHome/internal/aghos" + "github.com/AdguardTeam/AdGuardHome/internal/v1/agh" + "github.com/AdguardTeam/golibs/log" +) + +// signalHandler processes incoming signals and shuts services down. +type signalHandler struct { + signal chan os.Signal + + // services are the services that are shut down before application + // exiting. + services []agh.Service +} + +// handle processes OS signals. +func (h *signalHandler) handle() { + defer log.OnPanic("signalHandler.handle") + + for sig := range h.signal { + log.Info("sighdlr: received signal %q", sig) + + if aghos.IsShutdownSignal(sig) { + h.shutdown() + } + } +} + +// Exit status constants. +const ( + statusSuccess = 0 + statusError = 1 +) + +// shutdown gracefully shuts down all services. +func (h *signalHandler) shutdown() { + ctx, cancel := ctxWithDefaultTimeout() + defer cancel() + + status := statusSuccess + + log.Info("sighdlr: shutting down services") + for i, service := range h.services { + err := service.Shutdown(ctx) + if err != nil { + log.Error("sighdlr: shutting down service at index %d: %s", i, err) + status = statusError + } + } + + log.Info("sighdlr: shutting down adguard home") + + os.Exit(status) +} + +// newSignalHandler returns a new signalHandler that shuts down svcs. +func newSignalHandler(svcs ...agh.Service) (h *signalHandler) { + h = &signalHandler{ + signal: make(chan os.Signal, 1), + services: svcs, + } + + aghos.NotifyShutdownSignal(h.signal) + + return h +} 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 new file mode 100644 index 00000000..bbaac005 --- /dev/null +++ b/internal/v1/websvc/websvc.go @@ -0,0 +1,228 @@ +// Package websvc contains the AdGuard Home web service. +// +// TODO(a.garipov): Add tests. +package websvc + +import ( + "context" + "crypto/tls" + "fmt" + "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" + httptreemux "github.com/dimfeld/httptreemux/v5" +) + +// Config is the AdGuard Home web service configuration structure. +type Config struct { + // TLS is the optional TLS configuration. If TLS is not nil, + // SecureAddresses must not be empty. + TLS *tls.Config + + // Addresses are the addresses on which to serve the plain HTTP API. + 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 []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 +// [agh.Service] that does nothing. +type Service struct { + tls *tls.Config + servers []*http.Server + start time.Time + timeout time.Duration +} + +// New returns a new properly initialized *Service. If c is nil, svc is a nil +// *Service that does nothing. +func New(c *Config) (svc *Service) { + if c == nil { + return nil + } + + svc = &Service{ + tls: c.TLS, + start: c.Start, + timeout: c.Timeout, + } + + mux := newMux(svc) + + for _, a := range c.Addresses { + addr := a.String() + errLog := log.StdLog("websvc: http: "+addr, log.ERROR) + svc.servers = append(svc.servers, &http.Server{ + Addr: addr, + Handler: mux, + ErrorLog: errLog, + ReadTimeout: c.Timeout, + WriteTimeout: c.Timeout, + IdleTimeout: c.Timeout, + ReadHeaderTimeout: c.Timeout, + }) + } + + for _, a := range c.SecureAddresses { + addr := a.String() + errLog := log.StdLog("websvc: https: "+addr, log.ERROR) + svc.servers = append(svc.servers, &http.Server{ + Addr: addr, + Handler: mux, + TLSConfig: c.TLS, + ErrorLog: errLog, + ReadTimeout: c.Timeout, + WriteTimeout: c.Timeout, + IdleTimeout: c.Timeout, + ReadHeaderTimeout: c.Timeout, + }) + } + + 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) { + addrs = make([]string, 0, len(svc.servers)) + for _, srv := range svc.servers { + addrs = append(addrs, srv.Addr) + } + + return addrs +} + +// handleGetHealthCheck is the handler for the GET /health-check HTTP API. +func (svc *Service) handleGetHealthCheck(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, "OK") +} + +// unit is a convenient alias for struct{}. +type unit = struct{} + +// type check +var _ agh.Service = (*Service)(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) { + if svc == nil { + return nil + } + + srvs := svc.servers + + wg := &sync.WaitGroup{} + wg.Add(len(srvs)) + for _, srv := range srvs { + go serve(srv, wg) + } + + wg.Wait() + + return nil +} + +// serve starts and runs srv and writes all errors into its log. +func serve(srv *http.Server, wg *sync.WaitGroup) { + addr := srv.Addr + defer log.OnPanic(addr) + + var l net.Listener + var err error + if srv.TLSConfig == nil { + l, err = net.Listen("tcp", addr) + } else { + l, err = tls.Listen("tcp", addr, srv.TLSConfig) + } + if err != nil { + srv.ErrorLog.Printf("starting srv %s: binding: %s", addr, err) + } + + // Update the server's address in case the address had the port zero, which + // would mean that a random available port was automatically chosen. + srv.Addr = l.Addr().String() + + log.Info("websvc: starting srv http://%s", srv.Addr) + wg.Done() + + err = srv.Serve(l) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + srv.ErrorLog.Printf("starting srv %s: %s", addr, err) + } +} + +// 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 + } + + var errs []error + for _, srv := range svc.servers { + serr := srv.Shutdown(ctx) + if serr != nil { + errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr)) + } + } + + if len(errs) > 0 { + return errors.List("shutting down") + } + + return nil +} diff --git a/internal/v1/websvc/websvc_test.go b/internal/v1/websvc/websvc_test.go new file mode 100644 index 00000000..de4a9f5d --- /dev/null +++ b/internal/v1/websvc/websvc_test.go @@ -0,0 +1,93 @@ +package websvc_test + +import ( + "context" + "io" + "net/http" + "net/netip" + "net/url" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/v1/websvc" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testTimeout = 1 * time.Second + +// 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: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")}, + SecureAddresses: nil, + Timeout: testTimeout, + Start: testStart, + } + + svc = websvc.New(c) + + err := svc.Start() + require.NoError(t, err) + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + t.Cleanup(cancel) + + err = svc.Shutdown(ctx) + require.NoError(t, err) + }) + + addrs := svc.Addrs() + require.Len(t, addrs, 1) + + 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.NoErrorf(t, err, "creating req") + + httpCli := &http.Client{ + Timeout: testTimeout, + } + resp, err := httpCli.Do(req) + require.NoErrorf(t, err, "performing req") + require.Equal(t, wantCode, resp.StatusCode) + + testutil.CleanupAndRequireSuccess(t, resp.Body.Close) + + body, err = io.ReadAll(resp.Body) + require.NoErrorf(t, err, "reading body") + + 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/main.go b/main.go index 505eb3e5..03ad2f03 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,6 @@ +//go:build !v1 +// +build !v1 + package main import ( diff --git a/main_v1.go b/main_v1.go new file mode 100644 index 00000000..6b5f3dea --- /dev/null +++ b/main_v1.go @@ -0,0 +1,21 @@ +//go:build v1 +// +build v1 + +package main + +import ( + "embed" + + "github.com/AdguardTeam/AdGuardHome/internal/v1/cmd" +) + +// Embed the prebuilt client here since we strive to keep .go files inside the +// internal directory and the embed package is unable to embed files located +// outside of the same or underlying directory. + +//go:embed build2 +var clientBuildFS embed.FS + +func main() { + cmd.Main(clientBuildFS) +} diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 0762fdf2..fbbe7169 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -2,7 +2,21 @@ -## v0.107.3: API changes +## v0.108.0: API changes + +## v0.107.12: API changes + +### `GET /control/blocked_services/services` + +* The new `GET /control/blocked_services/services` HTTP API allows inspecting + all available services. + +## v0.107.7: API changes + +### The new optional field `"ecs"` in `QueryLogItem` + +* The new optional field `"ecs"` in `GET /control/querylog` contains the IP + network from an EDNS Client-Subnet option from the request message if any. ### The new possible status code in `/install/configure` response. @@ -10,6 +24,8 @@ `POST /install/configure` which means that the specified password does not meet the strength requirements. +## v0.107.3: API changes + ### The new field `"version"` in `AddressesInfo` * The new field `"version"` in `GET /install/get_addresses` is the version of diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 85c372a3..ad57d807 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -874,6 +874,19 @@ 'summary': 'Set (dis)allowed clients, blocked hosts, etc.' 'tags': - 'clients' + '/blocked_services/services': + 'get': + 'tags': + - 'blocked_services' + 'operationId': 'blockedServicesAvailableServices' + 'summary': 'Get available services to use for blocking' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BlockedServicesArray' '/blocked_services/list': 'get': 'tags': @@ -1396,7 +1409,6 @@ 'required': - 'enabled' - 'id' - - 'last_updated' - 'name' - 'rules_count' - 'url' @@ -1434,6 +1446,10 @@ 'type': 'array' 'items': '$ref': '#/components/schemas/Filter' + 'whitelist_filters': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/Filter' 'user_rules': 'type': 'array' 'items': @@ -1451,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' @@ -1860,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' @@ -1905,6 +1933,12 @@ - 'doq' - 'dnscrypt' - '' + 'ecs': + 'type': 'string' + 'example': '192.168.0.0/16' + 'description': > + The IP network defined by an EDNS Client-Subnet option in the + request message if any. 'elapsedMs': 'type': 'string' 'example': '54.023928' diff --git a/openapi/v1.yaml b/openapi/v1.yaml new file mode 100644 index 00000000..77eb1a09 --- /dev/null +++ b/openapi/v1.yaml @@ -0,0 +1,5033 @@ +'openapi': '3.0.3' +'info': + 'contact': + 'email': 'devteam@adguard.com' + 'name': 'AdGuard Home' + 'url': 'https://github.com/AdguardTeam/AdGuardHome' + 'description': | + **!! WARNING! API IS AT THE DRAFT STAGE! THINGS WILL BREAK! !!** + + AdGuard Home REST API, V1 **DRAFT**. Our administration web interface is + built on top of this REST API. + + This API is currently a **DRAFT** and is not covered by any stability + guarantees. Once this API reaches maturity, the old `/control/` API will + mostly be removed. + + ## Information For API Users + + * Empty arrays are always sent by the backend, unless documented + otherwise. If the backend doesn't, it's a backend error. + + * `PATCH` requests with JSON bodies use RFC 7396 JSON Merge Patch unless + documented otherwise. + + * The property `x-error-class` on plain text error responses suggests, + which class of error should be used if the API user wants to wrap it + into an object. The property `x-error-code` suggest the error code for + the error object. The code usually goes into the `code` property, and + the content, into `msg`. + + * The property `x-skip-web-api` on operations suggests API clients for + web, like our frontend, to skip this operation in their generated code. + + * The header `Server` will be set to `AdGuardHome/<>`. For + example: `AdGuardHome/v0.107.0-a.42+abcd1234`. + + ## Conventions For API Authors + + ### Naming + + * `CapitalCamelCase` for entities. + + * Initialisms are spelled like `DhcpSettings` and not `DHCPSettings`. + + * `lower_snake_case` for path and query parameters. + + * No unit suffices. + + * Path parameters's names start with `Path`; query, with `Query`. + + * Requests end with `Req`; responses, with `Resp`. + + ### Structure + + * Add `400` and `422` responses to requests that accept data. + + * Add `401` responses, unless the method can work without authorization. + + * Add `500` responses. + + * Descriptions are always on their own lines. + + * Don't add a description if there is already one a level above. + + * Five levels of indentation max, except for descriptions and array + items. + + * Keep things in alphabetical order. + + * Mark required things as such. Document possibly-absent fields **both** + in `required` **and** in `description`, if there is one. + + * Prefer flat objects. Example: `resp.top_user`, not `resp.top.user.val`. + + * Prefer to make it easier for the frontend where possible. + + * Provide examples for requests and responses. If examples are provided + elsewhere, document that. + + * Summaries and descriptions with dots. + + * Top-level value in a JSON request or response must be an object. + + ### Types + + * Add `'maximum': 65535` for 16-bit unsigned integers (for example, port + numbers). + + * Add `'minimum': 0` for unsigned integers. + + * Duration in milliseconds. Time in milliseconds in the Unix epoch. Both + of type `double`, because that is easier for the JS frontend. + + * Integers are always `int64`, numbers are always `double. + + 'license': + 'name': 'GNU General Public License v3.0' + 'url': 'https://www.gnu.org/licenses/gpl-3.0.txt' + 'title': 'AdGuard Home V1 DRAFT API' + 'version': '0.108' + +'servers': +- 'description': > + The V1 HTTP API namespace. + 'url': '/api/v1' + +'security': +- 'basicAuth': [] + +'tags': +- 'description': > + Authorization and account management. + 'name': 'accounts' +- 'description': > + Configuration and settings for Apple products. + 'name': 'apple' +- 'description': > + Runtime and persistent client information. + 'name': 'clients' +- 'description': > + DHCP server methods. + 'name': 'dhcp' +- 'description': > + First-time install configuration handlers. Will not be available once the + installation is done. + 'name': 'install' +- 'description': > + Query logs. + 'name': 'log' +- 'description': > + Filter lists, blocked services, and custom filtering rules. + 'name': 'protection' +- 'description': > + Settings management. + 'name': 'settings' +- 'description': > + Query, filtering, system, and other statistics. + 'name': 'stats' +- 'description': > + Information about the AdGuard Home server and the host system. + 'name': 'system' + +'paths': + '/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.' + 'tags': + - 'system' + + '/accounts/profile': + 'get': + 'operationId': 'GetV1AccountsProfile' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AccountsProfileResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get the profile of the current user.' + 'tags': + - 'accounts' + 'patch': + 'operationId': 'PatchV1AccountsProfile' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1AccountsProfileReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1AccountsProfileResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update the profile of the current user.' + 'tags': + - 'accounts' + + '/accounts/session': + 'delete': + 'operationId': 'DeleteV1AccountsSession' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Log out of the system.' + 'tags': + - 'accounts' + 'post': + 'operationId': 'PostV1AccountsSession' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1AccountsSessionReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Log into the system.' + 'tags': + - 'accounts' + + '/apple/doh.mobileconfig': + 'get': + 'operationId': 'GetV1AppleDohMobileconfig' + 'parameters': + - '$ref': '#/components/parameters/QueryClientId' + - '$ref': '#/components/parameters/QueryHost' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AppleDohMobileconfigResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get a DNS-over-HTTPS .mobileconfig.' + 'tags': + - 'apple' + 'x-skip-web-api': true + + '/apple/dot.mobileconfig': + 'get': + 'operationId': 'GetV1AppleDotMobileconfig' + 'parameters': + - '$ref': '#/components/parameters/QueryHost' + - '$ref': '#/components/parameters/QueryClientId' + 'responses': + '200': + '$ref': '#/components/responses/GetV1AppleDotMobileconfigResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get a DNS-over-HTTPS .mobileconfig.' + 'tags': + - 'apple' + 'x-skip-web-api': true + + '/clients/persistent': + 'get': + 'operationId': 'GetV1ClientsPersistent' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ClientsPersistentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all persistent clients.' + 'tags': + - 'clients' + 'post': + 'operationId': 'PostV1ClientsPersistent' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ClientsPersistentReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ClientsPersistentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Create a new persistent client.' + 'tags': + - 'clients' + + '/clients/persistent/{client_uid}': + 'delete': + 'operationId': 'DeleteV1ClientPersistent' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a persistent client.' + 'tags': + - 'clients' + 'parameters': + - '$ref': '#/components/parameters/PathClientUid' + 'patch': + 'operationId': 'PatchV1ClientPersistent' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1ClientPersistentReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1ClientPersistentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update a persistent client.' + 'tags': + - 'clients' + + '/clients/runtime': + 'get': + 'operationId': 'GetV1ClientsRuntime' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ClientsRuntimeResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all runtime clients.' + 'tags': + - 'clients' + + '/dhcp/leases': + 'get': + 'operationId': 'GetV1DhcpLeases' + 'responses': + '200': + '$ref': '#/components/responses/GetV1DhcpLeasesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all dynamic and static DHCP leases.' + 'tags': + - 'dhcp' + 'post': + 'operationId': 'PostV1DhcpLeases' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1DhcpLeasesReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1DhcpLeasesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Create a new static DHCP lease.' + 'tags': + - 'dhcp' + + '/dhcp/leases/{lease_uid}': + 'delete': + 'operationId': 'DeleteV1DhcpLease' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a static DHCP lease.' + 'tags': + - 'dhcp' + 'parameters': + - '$ref': '#/components/parameters/PathLeaseUid' + 'patch': + 'operationId': 'PatchV1DhcpLease' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1DhcpLeaseReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1DhcpLeaseResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update a static DHCP lease.' + 'tags': + - 'dhcp' + + '/dhcp/status': + 'get': + 'operationId': 'GetV1DhcpStatus' + 'responses': + '200': + '$ref': '#/components/responses/GetV1DhcpStatusResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get DHCP server status.' + 'tags': + - 'dhcp' + + '/install/check': + 'post': + 'operationId': 'PostV1InstallCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1InstallCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1InstallCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check initial configuration.' + 'tags': + - 'install' + + '/install/configure': + 'post': + 'operationId': 'PostV1InstallConfigure' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1InstallConfigureReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Apply initial configuration.' + 'tags': + - 'install' + + '/install/info': + 'get': + 'operationId': 'GetV1InstallInfo' + 'responses': + '200': + '$ref': '#/components/responses/GetV1InstallInfoResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get initial configuration information.' + 'tags': + - 'install' + + '/log/clear': + 'post': + 'operationId': 'PostV1LogClear' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1LogClearReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Clear the whole query log.' + 'tags': + - 'log' + + '/log/search': + 'get': + 'operationId': 'GetV1LogSearch' + 'parameters': + - '$ref': '#/components/parameters/QueryBefore' + - '$ref': '#/components/parameters/QueryLimit' + - '$ref': '#/components/parameters/QueryReason' + - '$ref': '#/components/parameters/QueryTerm' + 'responses': + '200': + '$ref': '#/components/responses/GetV1LogSearchResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Search query logs.' + 'tags': + - 'log' + + '/protection/blocked_services': + 'get': + 'operationId': 'GetV1ProtectionBlockedServices' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get blocked services.' + 'tags': + - 'protection' + 'put': + 'operationId': 'PutV1ProtectionBlockedServices' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1ProtectionBlockedServicesReq' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Replace blocked services.' + 'tags': + - 'protection' + + '/protection/check_custom_rules': + 'post': + 'operationId': 'PostV1ProtectionCheckCustomRules' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionCheckCustomRulesReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionCheckCustomRulesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check custom filtering rules.' + 'tags': + - 'protection' + + '/protection/custom_rules': + 'get': + 'operationId': 'GetV1ProtectionCustomRules' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionCustomRulesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get custom rules.' + 'tags': + - 'protection' + 'put': + 'operationId': 'PutV1ProtectionCustomRules' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1ProtectionCustomRulesReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Replace custom rules.' + 'tags': + - 'protection' + + '/protection/dns_rewrites': + 'get': + 'operationId': 'GetV1ProtectionDnsRewrites' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionDnsRewritesResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all classic DNS rewrites.' + 'tags': + - 'protection' + 'post': + 'operationId': 'PostV1ProtectionDnsRewrites' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionDnsRewritesReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionDnsRewritesResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Add a new classic DNS rewrite.' + 'tags': + - 'protection' + + '/protection/dns_rewrites/{dns_rewrite_uid}': + 'delete': + 'operationId': 'DeleteV1ProtectionDnsRewrite' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a classic DNS rewrite.' + 'tags': + - 'protection' + 'parameters': + - '$ref': '#/components/parameters/PathDnsRewriteUid' + + '/protection/filters': + 'get': + 'operationId': 'GetV1ProtectionFilters' + 'responses': + '200': + '$ref': '#/components/responses/GetV1ProtectionFiltersResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all filters.' + 'tags': + - 'protection' + 'post': + 'operationId': 'PostV1ProtectionFilters' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionFiltersReq' + 'responses': + '201': + '$ref': '#/components/responses/PostV1ProtectionFiltersResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Add a new filter.' + 'tags': + - 'protection' + + '/protection/filters/{filter_uid}': + 'delete': + 'operationId': 'DeleteV1ProtectionFilter' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Delete a filter.' + 'tags': + - 'protection' + 'parameters': + - '$ref': '#/components/parameters/PathFilterUid' + 'patch': + 'operationId': 'PatchV1ProtectionFilter' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1ProtectionFilterReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1ProtectionFilterResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '404': + '$ref': '#/components/responses/NotFoundResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Update a filter's settings. + 'tags': + - 'protection' + + '/protection/refresh_filters': + 'post': + 'operationId': 'PostV1ProtectionRefreshFilters' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFiltersReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1ProtectionRefreshFiltersResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Refresh all filters. + 'tags': + - 'protection' + + '/protection/refresh_filters/{filter_uid}': + 'parameters': + - '$ref': '#/components/parameters/PathFilterUid' + 'post': + 'operationId': 'PostV1ProtectionRefreshFilter' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFilterReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1ProtectionRefreshFilterResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': > + Refresh a filter. + 'tags': + - 'protection' + + '/settings/all': + 'get': + 'operationId': 'GetV1SettingsAll' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SettingsAllResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all settings.' + 'tags': + - 'settings' + + '/settings/dhcp': + 'patch': + 'operationId': 'PatchV1SettingsDhcp' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsDhcpReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsDhcpResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DHCP server settings.' + 'tags': + - 'settings' + + '/settings/dns': + 'patch': + 'operationId': 'PatchV1SettingsDns' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsDnsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsDnsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DNS server settings.' + 'tags': + - 'settings' + + '/settings/dns/access': + 'get': + 'description': > + Get DNS access settings. This is a separate API, because these lists + can become quite big. + 'operationId': 'GetV1SettingsDnsAccess' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SettingsDnsAccessResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get DNS access settings.' + 'tags': + - 'settings' + 'put': + 'description': > + Update DNS access settings. This is a separate API, because these lists + can become quite big. + 'operationId': 'PutV1SettingsDnsAccess' + 'requestBody': + '$ref': '#/components/requestBodies/PutV1SettingsDnsAccessReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update DNS access settings.' + 'tags': + - 'settings' + + '/settings/dns/check': + 'post': + 'operationId': 'PostV1SettingsDnsCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SettingsDnsCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SettingsDnsCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check DNS upstream settings.' + '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' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsLogReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsLogResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update query logging settings.' + 'tags': + - 'settings' + + '/settings/protection': + 'patch': + 'operationId': 'PatchV1SettingsProtection' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsProtectionReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsProtectionResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update protection settings.' + 'tags': + - 'settings' + + '/settings/stats': + 'patch': + 'operationId': 'PatchV1SettingsStats' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsStatsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsStatsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update statistics settings.' + 'tags': + - 'settings' + + '/settings/tls': + 'patch': + 'operationId': 'PatchV1SettingsTls' + 'requestBody': + '$ref': '#/components/requestBodies/PatchV1SettingsTlsReq' + 'responses': + '200': + '$ref': '#/components/responses/PatchV1SettingsTlsResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update TLS and encryption settings.' + 'tags': + - 'settings' + + '/settings/tls/check': + 'post': + 'operationId': 'PostV1SettingsTlsCheck' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SettingsTlsCheckReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SettingsTlsCheckResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Check TLS and encryption settings.' + 'tags': + - 'settings' + + '/stats/all': + 'get': + 'operationId': 'GetV1StatsAll' + 'responses': + '200': + '$ref': '#/components/responses/GetV1StatsAllResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get all statistics.' + 'tags': + - 'stats' + + '/stats/clear': + 'post': + 'operationId': 'PostV1StatsClear' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1StatsClearReq' + 'responses': + '204': + '$ref': '#/components/responses/NoContentResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Clear all statistics.' + 'tags': + - 'stats' + + '/system/info': + 'get': + 'operationId': 'GetV1SystemInfo' + 'responses': + '200': + '$ref': '#/components/responses/GetV1SystemInfoResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Get server information.' + 'tags': + - 'system' + + '/system/reset': + 'post': + 'operationId': 'PostV1SystemReset' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SystemResetReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SystemResetResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Reset all settings to defaults.' + 'tags': + - 'system' + + '/system/update': + 'post': + 'operationId': 'PostV1SystemUpdate' + 'requestBody': + '$ref': '#/components/requestBodies/PostV1SystemUpdateReq' + 'responses': + '200': + '$ref': '#/components/responses/PostV1SystemUpdateResp' + '400': + '$ref': '#/components/responses/BadRequestResp' + '401': + '$ref': '#/components/responses/UnauthorizedResp' + '422': + '$ref': '#/components/responses/UnprocessableEntityResp' + '500': + '$ref': '#/components/responses/InternalServerErrorResp' + 'summary': 'Update AdGuard Home.' + 'tags': + - 'system' + +'components': + 'parameters': + 'PathDnsRewriteUid': + 'description': > + DNS rewrite ID. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'dns_rewrite_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathClientUid': + 'description': > + The unique ID of a client. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'client_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathFilterUid': + 'description': > + The ID of a filter. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'filter_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'PathLeaseUid': + 'description': > + The ID of a static lease. + 'example': 'abcd1234' + 'in': 'path' + 'name': 'lease_uid' + 'required': true + 'schema': + '$ref': '#/components/schemas/Uid' + + 'QueryBefore': + 'description': > + Unix time, before which to show the search results, in milliseconds. + 'example': 1614345496000 + 'in': 'query' + 'name': 'before' + 'required': false + 'schema': + 'format': 'double' + 'type': 'number' + + 'QueryClientId': + 'description': > + ClientID, **not** its UID. + 'example': 'client-1' + 'in': 'query' + 'name': 'client_id' + 'required': false + 'schema': + '$ref': '#/components/schemas/ClientId' + + 'QueryHost': + 'description': > + The host for which the Configuration is generated. + 'example': 'example.org' + 'in': 'query' + 'name': 'host' + 'required': true + 'schema': + 'type': 'string' + + 'QueryLimit': + 'description': > + Maximum amount of records to return. + 'example': 100 + 'in': 'query' + 'name': 'limit' + 'required': false + 'schema': + 'format': 'int64' + 'type': 'integer' + + 'QueryReason': + 'description': > + Filter query log results by filtering reason. + 'example': 'not_filtered_notfound' + 'in': 'query' + 'name': 'reason' + 'required': false + 'schema': + '$ref': '#/components/schemas/FilteringReason' + + 'QueryTerm': + 'description': > + Search term. + 'example': '127.0.0.1' + 'in': 'query' + 'name': 'term' + 'required': false + 'schema': + 'type': 'string' + + 'requestBodies': + 'PatchV1AccountsProfileReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1AccountsProfileReq' + 'required': true + + 'PatchV1ClientPersistentReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ClientPersistentReq' + 'required': true + + 'PatchV1DhcpLeaseReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1DhcpLeaseReq' + 'required': true + + 'PatchV1ProtectionFilterReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ProtectionFilterReq' + 'required': true + + 'PatchV1SettingsDhcpReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDhcpReq' + 'required': true + + 'PatchV1SettingsDnsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDnsReq' + 'required': true + + 'PatchV1SettingsHttpReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsHttpReq' + 'required': true + + 'PatchV1SettingsLogReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsLogReq' + 'required': true + + 'PatchV1SettingsProtectionReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsProtectionReq' + 'required': true + + 'PatchV1SettingsStatsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsStatsReq' + 'required': true + + 'PatchV1SettingsTlsReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsTlsReq' + 'required': true + + 'PostV1AccountsSessionReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1AccountsSessionReq' + 'required': true + + 'PostV1ClientsPersistentReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ClientsPersistentReq' + 'required': true + + 'PostV1DhcpLeasesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1DhcpLeasesReq' + 'required': true + + 'PostV1InstallCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallCheckReq' + 'required': true + + 'PostV1InstallConfigureReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallConfigureReq' + 'required': true + + 'PostV1LogClearReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1LogClearReq' + 'required': true + + 'PostV1ProtectionCheckCustomRulesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesReq' + 'required': true + + 'PostV1ProtectionDnsRewritesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesReq' + 'required': true + + 'PostV1ProtectionFiltersReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionFiltersReq' + 'required': true + + 'PostV1ProtectionRefreshFilterReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterReq' + 'required': true + + 'PostV1ProtectionRefreshFiltersReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersReq' + 'required': true + + 'PostV1SettingsDnsCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsDnsCheckReq' + 'required': true + + 'PostV1SettingsTlsCheckReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsTlsCheckReq' + 'required': true + + 'PostV1StatsClearReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1StatsClearReq' + 'required': true + + 'PostV1SystemResetReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemResetReq' + 'required': true + + 'PostV1SystemUpdateReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemUpdateReq' + 'required': true + + 'PutV1ProtectionBlockedServicesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1ProtectionBlockedServicesReq' + 'required': true + + 'PutV1ProtectionCustomRulesReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1ProtectionCustomRulesReq' + 'required': true + + 'PutV1SettingsDnsAccessReq': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PutV1SettingsDnsAccessReq' + 'required': true + + 'responses': + 'BadRequestResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BadRequestResp' + 'text/plain': + 'example': >- + invalid character '{' looking for beginning of object key string + 'x-error-class': '#/components/schemas/BadRequestResp' + 'x-error-code': 'TXT400' + 'description': > + Generic bad request response. Sent when the request data is malformed + (for example, invalid JSON). + + 'GetV1AccountsProfileResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1AccountsProfileResp' + 'description': > + A successful response to a `GET /api/v1/accounts/profile` request. + + 'GetV1AppleDohMobileconfigResp': + 'content': + 'application/xml': + 'schema': + '$ref': '#/components/schemas/GetV1AppleDohMobileconfigResp' + 'description': > + A successful response to a `GET /api/v1/apple/doh.mobileconfig` request. + + 'GetV1AppleDotMobileconfigResp': + 'content': + 'application/xml': + 'schema': + '$ref': '#/components/schemas/GetV1AppleDotMobileconfigResp' + 'description': > + A successful response to a `GET /api/v1/apple/dot.mobileconfig` request. + + 'GetV1ClientsPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ClientsPersistentResp' + 'description': > + A successful response to a `GET /api/v1/clients/persistent` request. + + 'GetV1ClientsRuntimeResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ClientsRuntimeResp' + 'description': > + A successful response to a `GET /api/v1/clients/runtime` request. + + 'GetV1DhcpLeasesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1DhcpLeasesResp' + 'description': > + A successful response to a `GET /api/v1/dhcp/leases` request. + + 'GetV1DhcpStatusResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1DhcpStatusResp' + 'description': > + A successful response to a `GET /api/v1/dhcp/status` request. + + 'GetV1InstallInfoResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1InstallInfoResp' + 'description': > + A successful response to a `GET /api/v1/install/info` request. + + 'GetV1LogSearchResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1LogSearchResp' + 'description': > + A successful response to a `GET /api/v1/log/search` request. + + 'GetV1ProtectionBlockedServicesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionBlockedServicesResp' + 'description': > + A successful response to a `GET /api/v1/protection/blocked_services` + or a `PUT /api/v1/protection/blocked_services` request. + + 'GetV1ProtectionCustomRulesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionCustomRulesResp' + 'description': > + A successful response to a `GET /api/v1/protection/custom_rules` + request. + + 'GetV1ProtectionDnsRewritesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionDnsRewritesResp' + 'description': > + A successful response to a `GET /api/v1/protection/dns_rewrites` + request. + + 'GetV1ProtectionFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1ProtectionFiltersResp' + 'description': > + A successful response to a `GET /api/v1/protection/filters` request. + + 'GetV1SettingsAllResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SettingsAllResp' + 'description': > + A successful response to a `GET /api/v1/settings/all` request. + + 'GetV1SettingsDnsAccessResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SettingsDnsAccessResp' + 'description': > + A successful response to a `GET /api/v1/settings/dns/access` request. + + 'GetV1StatsAllResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1StatsAllResp' + 'description': > + A successful response to a `GET /api/v1/stats/all` request. + + 'GetV1SystemInfoResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetV1SystemInfoResp' + 'description': > + A successful response to a `GET /api/v1/server/info` request. + + 'InternalServerErrorResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/InternalServerErrorResp' + 'text/plain': + 'example': >- + runtime error: invalid memory address or nil pointer dereference + 'x-error-class': '#/components/schemas/InternalServerErrorResp' + 'x-error-code': 'TXT500' + 'description': > + Generic internal server error. + + 'NoContentResp': + 'description': > + Generic no-error no-content response. + + 'NotFoundResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/NotFoundResp' + 'text/plain': + 'example': >- + Not found. + 'x-error-class': '#/components/schemas/NotFoundResp' + 'x-error-code': 'TXT404' + 'description': > + Generic not found response. + + 'PatchV1AccountsProfileResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1AccountsProfileResp' + 'description': > + A successful response to a `PATCH /api/v1/accounts/profile` request. + + 'PatchV1ClientPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ClientPersistentResp' + 'description': > + A successful response to + a `PATCH /api/v1/clients/persistent/{client_uid}` request. + + 'PatchV1DhcpLeaseResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1DhcpLeaseResp' + 'description': > + A successful response to a `PATCH /api/v1/dhcp/leases/{lease_uid}` + request. + + 'PatchV1ProtectionFilterResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1ProtectionFilterResp' + 'description': > + A successful response to a `PATCH /api/v1/filters/{filter_uid}` request. + + 'PatchV1SettingsDhcpResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDhcpResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/dhcp` request. + + 'PatchV1SettingsDnsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsDnsResp' + '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': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsLogResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/log` request. + + 'PatchV1SettingsProtectionResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsProtectionResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/protection` request. + + 'PatchV1SettingsStatsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsStatsResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/stats` request. + + 'PatchV1SettingsTlsResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PatchV1SettingsTlsResp' + 'description': > + A successful response to a `PATCH /api/v1/settings/tls` request. + + 'PostV1ClientsPersistentResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ClientsPersistentResp' + 'description': > + A successful response to a `POST /api/v1/clients/persistent` request. + + 'PostV1DhcpLeasesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1DhcpLeasesResp' + 'description': > + A successful response to a `POST /api/v1/dhcp/leases` request. + + 'PostV1InstallCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1InstallCheckResp' + 'description': > + A successful response to a `POST /api/v1/install/check` request. + + 'PostV1ProtectionCheckCustomRulesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesResp' + 'description': > + A successful response to a `POST /api/v1/protection/check_custom_rules` + request. + + 'PostV1ProtectionDnsRewritesResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesResp' + 'description': > + A successful response to a `POST /api/v1/protection/dns_rewrites` + request. + + 'PostV1ProtectionFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionFiltersResp' + 'description': > + A successful response to a `POST /api/v1/protection/filters` request. + + 'PostV1ProtectionRefreshFilterResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterResp' + 'description': > + A successful response to + a `POST /api/v1/protection/refresh_filters/{filter_uid}` request. + + 'PostV1ProtectionRefreshFiltersResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersResp' + 'description': > + A successful response to a `POST /api/v1/protection/refresh_filters` + request. + + 'PostV1SettingsDnsCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsDnsCheckResp' + 'description': > + A successful response to a `POST /api/v1/settings/dns/check` request. + + 'PostV1SettingsTlsCheckResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SettingsTlsCheckResp' + 'description': > + A successful response to a `POST /api/v1/settings/tls/check` request. + + 'PostV1SystemResetResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemResetResp' + 'description': > + A successful response to a `POST /api/v1/system/reset` request. + + 'PostV1SystemUpdateResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/PostV1SystemUpdateResp' + 'description': > + A successful response to a `POST /api/v1/system/update` request. + + 'UnauthorizedResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/UnauthorizedResp' + 'text/plain': + 'example': 'no or bad authorization provided' + 'x-error-class': '#/components/schemas/UnauthorizedResp' + 'x-error-code': 'TXT401' + 'description': > + This API requires authorization. + 'headers': + 'WWW-Authenticate': + 'description': > + The required WWW-Authenticate header. + 'example': 'Basic realm="AdGuard Home", charset="UTF-8"' + 'required': true + 'schema': + 'type': 'string' + + 'UnprocessableEntityResp': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/UnprocessableEntityResp' + 'description': > + Generic bad request data response. Sent when the request data is + well-formed but is invalid for this request. + + 'schemas': + 'BadRequestResp': + 'example': + 'code': 'JSN000' + 'msg': >- + invalid character '{' looking for beginning of object key string + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'BlockedServiceId': + 'description': > + ID of a blocked service. + 'enum': + - '9gag' + - 'amazon' + - 'cloudflare' + - 'dailymotion' + - 'discord' + - 'disneyplus' + - 'ebay' + - 'epic_games' + - 'facebook' + - 'hulu' + - 'imgur' + - 'instagram' + - 'mail_ru' + - 'netflix' + - 'ok' + - 'origin' + - 'pinterest' + - 'qq' + - 'reddit' + - 'skype' + - 'snapchat' + - 'spotify' + - 'steam' + - 'telegram' + - 'tiktok' + - 'tinder' + - 'twitch' + - 'twitter' + - 'viber' + - 'vimeo' + - 'vk' + - 'wechat' + - 'weibo' + - 'whatsapp' + - 'youtube' + 'type': 'string' + + 'BlockedServices': + 'description': > + Blocked services. + 'example': + 'services': + - '9gag' + - 'dailymotion' + 'properties': + 'services': + 'description': > + All blocked services. + 'items': + '$ref': '#/components/schemas/BlockedServiceId' + 'type': 'array' + 'required': + - 'services' + 'type': 'object' + + 'Channel': + 'description': > + AdGuard Home release channel. + 'enum': + - 'beta' + - 'development' + - 'edge' + - 'release' + 'type': 'string' + + 'ClientId': + 'pattern': '[0-9a-z-]{1,64}' + 'type': 'string' + + 'ClientInfo': + 'description': > + A shorter information about a client. If the `uid` field is present, + this is a persistent client. Otherwise, this is a runtime client. + 'properties': + 'blocked': + 'description': > + If `true`, client is blocked. + 'type': 'boolean' + 'ids': + 'description': | + Client identifiers. That includes ClientIDs set by users as well as + IP addresses. There must be at least one identifier. + + Not to be confused with the `uid` field. + 'example': + - '1.2.3.4' + - 'user-1' + 'items': + 'type': 'string' + 'minItems': 1 + 'type': 'array' + 'name': + 'description': > + The name of the client, if any. If there are none, this field is + absent. + 'example': 'User 1' + 'type': 'string' + 'num': + 'description': > + Total number of requests for this client. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'num_blocked': + 'description': > + Total number of blocked requests for this client. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'uid': + '$ref': '#/components/schemas/Uid' + 'whois': + '$ref': '#/components/schemas/Whois' + 'required': + - 'blocked' + - 'ids' + - 'num' + - 'num_blocked' + 'type': 'object' + + 'CustomRules': + 'description': > + Custom filtering rules. + 'example': + 'rules': + - '||example.com' + - '# Some comment' + 'properties': + 'rules': + 'description': > + All custom filtering rules + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'rules' + 'type': 'object' + + 'DhcpLease': + 'allOf': + - '$ref': '#/components/schemas/DhcpLeasePost' + - 'description': > + A dynamic or static DHCP lease. If the `uid` field is present, this is + a static lease. Otherwise, this is a dynamic lease. + 'example': + 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'uid': 'abcd1234' + 'properties': + 'uid': + '$ref': '#/components/schemas/Uid' + + 'DhcpLeasePatch': + 'description': > + A static DHCP lease update object. + 'example': + 'expires': 1614345496000 + 'properties': + 'expires': + 'description': > + The Unix time of the lease's expiry time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'hostname': + 'description': > + Client's hostname. + 'type': 'string' + 'ip': + 'description': > + IP address leased to the client. + 'type': 'string' + 'mac': + 'description': > + Hardware address of the lease client. + 'type': 'string' + 'type': 'object' + + 'DhcpLeasePost': + 'allOf': + - '$ref': '#/components/schemas/DhcpLeasePatch' + - 'description': > + A static DHCP lease create object. + 'example': + 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'required': + - 'expires' + - 'hostname' + - 'ip' + - 'mac' + + 'DhcpSettings': + 'allOf': + - '$ref': '#/components/schemas/DhcpSettingsPatch' + - 'description': > + DHCP server settings. + 'example': + 'enabled': true + 'interface_name': 'wlan0' + 'ipv4_gateway_ip': '192.168.1.1' + 'ipv4_lease_duration': 86400000 + 'ipv4_range_end': '192.168.1.101' + 'ipv4_range_start': '192.168.1.2' + 'ipv4_subnet_mask': '255.255.255.0' + 'ipv6_range_start': '2001:db8::1' + 'ipv6_lease_duration': 86400000 + 'required': + - 'enabled' + + 'DhcpSettingsPatch': + 'description': > + DHCP server settings update object. + 'example': + 'enabled': true + 'interface_name': 'wlan0' + 'ipv4_gateway_ip': '192.168.1.1' + 'ipv4_lease_duration': 86400000 + 'ipv4_range_end': '192.168.1.101' + 'ipv4_range_start': '192.168.1.2' + 'ipv4_subnet_mask': '255.255.255.0' + 'properties': + 'enabled': + 'description': > + If `true`, the DHCP server is enabled. + 'type': 'boolean' + 'interface_name': + 'description': > + The name of network interface to serve on. + 'type': 'string' + 'ipv4_gateway_ip': + 'description': > + The IP address of the gateway. + 'type': 'string' + 'ipv4_lease_duration': + 'description': > + The duration of the IPv4 lease, in milliseconds. + 'type': 'number' + 'ipv4_range_end': + 'description': > + The end of the IPv4 addresses to serve to clients. + 'type': 'string' + 'ipv4_range_start': + 'description': > + The start of the IPv4 addresses to serve to clients. + 'type': 'string' + 'ipv4_subnet_mask': + 'description': > + The IP subnet mask. + 'type': 'string' + 'ipv6_lease_duration': + 'description': > + The duration of the IPv6 lease, in milliseconds. + 'type': 'number' + 'ipv6_range_start': + 'description': > + The start of the IPv6 addresses to serve to clients. + 'type': 'string' + 'type': 'object' + + 'DnsAccessSettings': + 'description': > + DNS server access settings. + 'example': + 'allowed_clients': [] + 'blocked_clients': + - '1.2.3.4' + - '5.6.7.8/16' + 'blocked_domain_rules': + - 'id.server' + - '*.example.org' + - '||example.com^' + 'properties': + 'allowed_clients': + 'description': > + CIDR or IP addresses of clients in the allowlist. If non-empty, + AdGuard Home will accept requests from these IP addresses only. + 'items': + 'type': 'string' + 'type': 'array' + 'blocked_clients': + 'description': > + CIDR or IP addresses of clients in the blocklist. If non-empty, + AdGuard Home will drop requests from these IP addresses. + 'items': + 'type': 'string' + 'type': 'array' + 'blocked_domain_rules': + 'description': > + AdGuard Home will drop DNS queries, if the domains in their queries + match these rules. Here you can specify the exact domain + names, wildcards, and `urlfilter` rules. Examples: + + * `example.org` + + * `*.example.org` + + * `||example.org^` + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'allowed_clients' + - 'blocked_clients' + - 'blocked_domain_rules' + 'type': 'object' + + 'DnsBlockingMode': + 'description': | + DNS blocking mode. + + * `custom_ip`: Respond with a custom IP address. If this mode is + selected, both `blocking_ipv4` and `blocking_ipv6` parameters must + be set. + + * `default`: Same as `null_ip` for Adblock-style rules, but respond + with the IP address specified in the rule when blocked by an + `/etc/hosts`-style rule. + + * `null_ip`: Respond with a zero IP address: `0.0.0.0` for `A` + requests and `::` for `AAAA` ones. + + * `nxdomain`: Respond with the `NXDOMAIN` code. + + * `refused`: Respond with the `REFUSET` code. + + 'enum': + - 'custom_ip' + - 'default' + - 'null_ip' + - 'nxdomain' + - 'refused' + 'type': 'string' + + 'DnsClass': + 'description': > + DNS resource record class, aka `CLASS`. + 'enum': + - 'any' + - 'ch' + - 'cs' + - 'hs' + - 'in' + 'type': 'string' + + 'DnsProto': + 'description': > + DNS protocol. + 'enum': + - 'dot' + - 'doh' + - 'doq' + - 'dnscrypt' + - 'udp' + 'type': 'string' + + 'DnsResponseCode': + 'description': > + DNS response code, aka `RCODE`. + 'enum': + - 'badalg' + - 'badcookie' + - 'badkey' + - 'badmode' + - 'badname' + - 'badsig' + - 'badtime' + - 'badtrunc' + - 'badvers' + - 'formerr' + - 'noerror' + - 'notauth' + - 'notimp' + - 'notzone' + - 'nxdomain' + - 'nxrrset' + - 'refused' + - 'servfail' + - 'yxdomain' + - 'yxrrset' + 'type': 'string' + + 'DnsRewrite': + 'allOf': + - '$ref': '#/components/schemas/DnsRewritePost' + - 'description': > + A classic DNS rewrite. + 'example': + 'answer': 'A' + 'domain': 'example.com' + 'id': 'abcd1234' + 'properties': + 'id': + '$ref': '#/components/schemas/Uid' + 'required': + - 'answer' + - 'domain' + - 'id' + 'type': 'object' + + 'DnsRewritePost': + 'description': > + A classic DNS rewrite create object. + 'example': + 'answer': 'A' + 'domain': 'example.com' + 'properties': + 'answer': + 'description': > + The value of an `A`, `AAAA`, or `CNAME` DNS record in the response. + Acceptable formats: + + * Domain name: add a `CNAME` record with this domain name. + + * IPv4 address: use this IP in an `A` response. + + * IPv6 address: use this IP in an `AAAA` response. + + * The literal `A`: keep only `A` records from the upstream + response. + + * The literal `AAAA`: keep only `AAAA` records from the upstream + response. + 'type': 'string' + 'domain': + 'description': > + Domain name or wildcard. + 'type': 'string' + 'required': + - 'answer' + - 'domain' + 'type': 'object' + + 'DnsSettings': + 'allOf': + - '$ref': '#/components/schemas/DnsSettingsPatch' + - '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' + - '149.112.112.10' + 'cache_size': 4194304 + 'cache_ttl_max': 0 + 'cache_ttl_min': 0 + 'dnssec': false + 'edns_client_subnet': false + 'ipv6': true + 'rate_limit': 20 + 'upstream_mode': 'load_balancing' + 'upstream_servers': + - '1.1.1.1' + - '8.8.8.8' + 'upstream_timeout': '1s' + 'required': + - 'addresses' + - 'blocking_mode' + - 'bootstrap_servers' + - 'cache_size' + - 'cache_ttl_max' + - 'cache_ttl_min' + - 'dnssec' + - 'edns_client_subnet' + - 'ipv6' + - 'rate_limit' + - 'upstream_mode' + - 'upstream_servers' + - 'upstream_timeout' + + 'DnsSettingsPatch': + 'description': > + DNS server settings update object. + 'example': + 'cache_size': 4194304 + '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`. + See the documentation for the `DnsBlockingMode` schema. If + `blocking_mode` is different from `custom_ip`, this property is not + included. + 'type': 'string' + 'blocking_ipv6': + 'description': > + IPv6 address to respond with when `blocking_mode` is `custom_ip`. + See the documentation for the `DnsBlockingMode` schema. If + `blocking_mode` is different from `custom_ip`, this property is not + included. + 'type': 'string' + 'blocking_mode': + '$ref': '#/components/schemas/DnsBlockingMode' + 'bootstrap_servers': + 'description': | + Bootstrap DNS servers' IP addresses to resolve the hostnames of the + encrypted DNS server providers. + 'items': + 'type': 'string' + 'type': 'array' + 'cache_size': + 'description': > + DNS cache size in bytes. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'cache_ttl_max': + 'description': > + Set a maximum time-to-live value for entries in the DNS cache. `0` + means no override. The value is in **seconds**, like in DNS record + headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'cache_ttl_min': + 'description': > + Extend short time-to-live values received from the upstream server + when caching DNS responses. `0` means no override. TThe value is + in **seconds**, like in DNS record headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'dnssec': + 'description': > + If `true`, set DNSSEC flag in outcoming DNS queries and check the + result. A DNSSEC-enabled resolver is required. + 'type': 'boolean' + 'edns_client_subnet': + 'description': > + If `true`, enable EDNS Client Subnet support and send clients' + subnets to DNS servers. + 'type': 'boolean' + 'ipv6': + 'description': > + If `true`, accept `AAAA` DNS queries. If `false`, respond to them + with an empty answer. + 'type': 'boolean' + 'rate_limit': + 'description': > + The number of requests per second that a single client is allowed to + make. `0` means no limit. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'upstream_mode': + '$ref': '#/components/schemas/DnsUpstreamMode' + 'upstream_servers': + 'description': > + Upstream DNS servers. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'upstream_timeout': + 'description': > + Upstream request timeout, as a human readable duration. + 'type': 'string' + 'type': 'object' + + 'DnsType': + 'description': > + DNS resource record type, aka `TYPE`. + 'enum': + - 'a' + - 'aaaa' + - 'afsdb' + - 'any' + - 'apl' + - 'atma' + - 'avc' + - 'axfr' + - 'caa' + - 'cdnskey' + - 'cds' + - 'cert' + - 'cname' + - 'csync' + - 'dhcid' + - 'dlv' + - 'dname' + - 'dnskey' + - 'ds' + - 'eid' + - 'eui48' + - 'eui64' + - 'gid' + - 'gpos' + - 'hinfo' + - 'hip' + - 'https' + - 'isdn' + - 'ixfr' + - 'key' + - 'kx' + - 'l32' + - 'l64' + - 'loc' + - 'lp' + - 'maila' + - 'mailb' + - 'mb' + - 'md' + - 'mf' + - 'mg' + - 'minfo' + - 'mr' + - 'mx' + - 'naptr' + - 'nid' + - 'nimloc' + - 'ninfo' + - 'ns' + - 'nsap-ptr' + - 'nsec' + - 'nsec3' + - 'nsec3param' + - 'null' + - 'nxt' + - 'openpgpkey' + - 'opt' + - 'ptr' + - 'px' + - 'rkey' + - 'rp' + - 'rrsig' + - 'rt' + - 'sig' + - 'smimea' + - 'soa' + - 'spf' + - 'srv' + - 'sshfp' + - 'svcb' + - 'ta' + - 'talink' + - 'tkey' + - 'tlsa' + - 'tsig' + - 'txt' + - 'uid' + - 'uinfo' + - 'unspec' + - 'uri' + - 'x25' + 'type': 'string' + + 'DnsUpstreamMode': + 'description': | + Upstream request mode. + + * `fastest`: Query all DNS servers and return the IP address that was + returned by the fastest response. Slows down DNS responses, since + it waits for responses from all upstreams, but improves the overall + connectivity. + + * `load_balancing`: Query one server at a time using a weighted random + algorithm picking the server so that the fastest server is used + more often. + + * `parallel`: Use parallel requests to speed up resolving by + simultaneously querying all upstream servers. + 'enum': + - 'fastest' + - 'load_balancing' + - 'parallel' + 'type': 'string' + + 'ErrorCode': + 'description': | + An error code. + + * `AUT000`: No or bad authorization credentials provided. + + * `ENT404`: Entity not found; as opposed to path not found. + + * `JSN000`: A JSON syntax error. + + * `JSN001`: A JSON type error. + + * `OSS000`: The server's operating system doesn't support the + requested functionality. + + * `PTH404`: Path not found; as opposed to entity not found. + + * `RNT000`: A server runtime error. + + * `TXT400`: A plaintext bad request error. Used when a plaintext + error is wrapped. + + * `TXT401`: A plaintext unauthorized error. Used when a plaintext + error is wrapped. + + * `TXT404`: A plaintext not found error. Used when a plaintext error + is wrapped. + + * `TXT500`: A plaintext internal server error. Used when a plaintext + error is wrapped. + + TODO(a.garipov): Expand with TLS validation errors, DHCP errors, filter + URL reaching errors, OS and I/O errors, and so on. + 'enum': + - 'AUT000' + - 'ENT404' + - 'JSN000' + - 'JSN001' + - 'OSS000' + - 'PTH404' + - 'RNT000' + - 'TXT400' + - 'TXT401' + - 'TXT404' + - 'TXT500' + 'type': 'string' + + 'Filter': + 'allOf': + - '$ref': '#/components/schemas/FilterPatch' + - 'description': > + A single filter list of rules. + 'example': + 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + 'properties': + 'num_rules': + 'description': > + Number of rules in this filter. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'refreshed': + 'description': > + Unix time of last refresh for this filter, in milliseconds. + 'format': 'double' + 'type': 'number' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'allowlist' + - 'enabled' + - 'name' + - 'num_rules' + - 'refreshed' + - 'uid' + - 'url' + + 'FilterPatch': + 'description': > + A filter update object. + 'example': + 'enabled': true + 'properties': + 'allowlist': + 'description': > + If `true`, this filter works as an allowlist filters. + 'type': 'boolean' + 'enabled': + 'description': > + If `true`, this filter is applied. + 'type': 'boolean' + 'name': + 'description': > + The name of this filter. + 'type': 'string' + 'url': + 'description': | + A URL of the file containing filtering rules. + + Examples of allowed schemes: + + * `file:///home/user/ads/rules.txt`: A local file. + + * `http://example.com/ads/rules.txt`: Remote list, fetched over + plain HTTP. + + * `https://example.com/ads/rules.txt`: Remote list, fetched over + HTTPS. + 'type': 'string' + 'type': 'object' + + 'FilterPost': + 'allOf': + - '$ref': '#/components/schemas/FilterPatch' + - 'description': > + A filter create object. + 'example': + 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'url': 'https://admaster.example.com/list.txt' + 'required': + - 'allowlist' + - 'enabled' + - 'name' + - 'url' + + 'FilteringReason': + 'description': > + Request filtering status. + 'enum': + - 'filtered_blocked_service' + - 'filtered_blocklist' + - 'filtered_invalid' + - 'filtered_parental' + - 'filtered_safe_browsing' + - 'filtered_safe_search' + - 'not_filtered_allowlist' + - 'not_filtered_error' + - 'not_filtered_notfound' + - 'rewrite' + - 'rewrite_etc_hosts' + - 'rewrite_rule' + 'type': 'string' + + 'FilteringResultRule': + 'description': > + Applied filtering rule. + 'properties': + 'filter_list_uid': + '$ref': '#/components/schemas/Uid' + 'text': + 'description': > + The text of the filtering rule applied to the request, if any. + 'type': 'string' + 'required': + - 'filter_list_uid' + - 'text' + 'type': 'object' + + 'GetV1AccountsProfileResp': + '$ref': '#/components/schemas/Profile' + + # TODO(a.garipov): Find a way to describe such XML documents using OpenAPI. + # If that is even possible. + 'GetV1AppleDohMobileconfigResp': + 'example': | + + + + + PayloadContent + + + DNSSettings + + DNSProtocol + HTTPS + ServerName + example.com + ServerURL + https://example.com/dns-query/123 + + Name + myexample.local DoH + PayloadDescription + Configures device to use AdGuard Home + PayloadDisplayName + myexample.local DoH + PayloadIdentifier + com.apple.dnsSettings.managed.b6928468-ae3a-4368-a70d-cb7122275013 + PayloadType + com.apple.dnsSettings.managed + PayloadUUID + 18526b8c-6065-4b96-b635-9cde769ac0f2 + PayloadVersion + 1 + + + PayloadDescription + Adds AdGuard Home to Big Sur and iOS 14 or newer systems + PayloadDisplayName + myexample.local DoH + PayloadIdentifier + 9a37b659-7541-4f9e-8b4d-6e2a59a123c8 + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadUUID + 255dbaf7-0c52-4855-9b22-ad8209690197 + PayloadVersion + 1 + + + 'type': 'object' + + # TODO(a.garipov): See the comment on GetV1AppleDohMobileconfigResp. + 'GetV1AppleDotMobileconfigResp': + 'example': | + + + + + PayloadContent + + + DNSSettings + + DNSProtocol + TLS + ServerName + 123.example.com + + Name + example.com DoT + PayloadDescription + Configures device to use AdGuard Home + PayloadDisplayName + example.com DoT + PayloadIdentifier + com.apple.dnsSettings.managed.7807cb66-c6ec-4c78-be29-d8ffcb3321ee + PayloadType + com.apple.dnsSettings.managed + PayloadUUID + b0fb9137-e27a-4f95-abc3-556103ad9ac1 + PayloadVersion + 1 + + + PayloadDescription + Adds AdGuard Home to Big Sur and iOS 14 or newer systems + PayloadDisplayName + myexample.local DoT + PayloadIdentifier + f1095036-406e-4243-8210-cf0ffa52b3f6 + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadUUID + 21cd3597-0769-486a-86d0-7b5e32d24305 + PayloadVersion + 1 + + + 'type': 'object' + + 'GetV1ClientsPersistentResp': + 'description': > + Persistent clients. + 'example': + 'clients': + - 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'uid': 'abcd1234' + 'upstream_servers': [] + - 'blocked': false + 'blocked_services': [] + 'filtering': true + 'ids': ['client-2'] + 'name': 'Client 2' + 'parental': true + 'safe_browsing': true + 'safe_search': true + 'tags': ['user_child'] + 'use_global_blocked_services': false + 'use_global_settings': false + 'uid': 'efgh5678' + 'upstream_servers': [] + 'properties': + 'clients': + 'description': > + All persistent clients. + 'items': + '$ref': '#/components/schemas/PersistentClient' + 'type': 'array' + 'required': + - 'clients' + 'type': 'object' + + 'GetV1ClientsRuntimeResp': + 'description': > + Runtime clients. + 'example': + 'clients': + - 'host': 'my-box' + 'ip': '1.2.3.4' + 'num_blocked_requests': 0 + 'num_requests': 100 + 'sources': + - 'arp' + - 'ip': '5.6.7.8' + 'num_blocked_requests': 100 + 'num_requests': 100 + 'sources': + - 'whois' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + 'properties': + 'clients': + 'description': > + All runtime clients. + 'items': + '$ref': '#/components/schemas/RuntimeClient' + 'type': 'array' + 'required': + - 'clients' + 'type': 'object' + + 'GetV1DhcpLeasesResp': + 'description': > + All dynamic and static DHCP leases. + 'example': + 'leases': + - 'expires': 1614345496000 + 'hostname': 'my-mobile' + 'ip': '192.168.1.2' + 'mac': '01:23:45:67:89:ab' + 'uid': 'abcd1234' + - 'expires': 1614345497000 + 'hostname': '' + 'ip': '192.168.1.3' + 'mac': '01:23:45:67:89:cd' + 'properties': + 'leases': + 'description': > + Dynamic and static DHCP leases. + 'items': + '$ref': '#/components/schemas/DhcpLease' + 'type': 'array' + 'required': + - 'leases' + 'type': 'object' + + 'GetV1DhcpStatusResp': + 'description': > + Current DHCP server status and data for enabling it. + 'example': + 'interfaces': + - 'ips': + - '192.168.1.1' + 'mac': '01:23:45:67:89:ab' + 'mtu': 1500 + 'name': 'lan0' + 'up': true + 'ipv4_other_servers': + 'ips': + - '192.169.1.1' + 'ipv4_static_ip': + 'ip': '192.168.1.1' + 'static': true + 'supported': true + 'ipv6_other_servers': + 'ips': [] + 'error': 'permission denied' + 'ipv6_static_ip': + 'ip': '200f::1' + 'static': true + 'supported': true + 'properties': + 'interfaces': + 'description': > + Available network interfaces. + 'items': + '$ref': '#/components/schemas/NetworkInterface' + 'type': 'array' + 'ipv4_other_servers': + '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer' + 'ipv4_static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'ipv6_other_servers': + '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer' + 'ipv6_static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'required': + - 'interfaces' + - 'ipv4_other_servers' + - 'ipv4_static_ip' + - 'ipv6_other_servers' + - 'ipv6_static_ip' + 'type': 'object' + + 'GetV1DhcpStatusRespOtherServer': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'ips': + 'description': > + IP addresses of other DHCP servers, if found. + 'required': + - 'ips' + 'type': 'object' + + 'GetV1InstallInfoResp': + 'description': > + AdGuard Home addresses configuration. + 'example': + 'dns_port': 53 + 'interfaces': + - 'ips': + - '192.168.1.1' + 'mac': '01:23:45:67:89:ab' + 'mtu': 1500 + 'name': 'lan0' + 'up': true + 'web_port': 80 + 'properties': + 'dns_port': + 'description': > + Recommended DNS port. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'interfaces': + 'description': > + Available network interfaces. + 'items': + '$ref': '#/components/schemas/NetworkInterface' + 'type': 'array' + 'web_port': + 'description': > + Recommended web interface port. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'dns_port' + - 'interfaces' + - 'web_port' + 'type': 'object' + + 'GetV1LogSearchResp': + 'description': > + Query log search results. + 'example': + 'results': + - 'answer': + - 'ttl': 60 + 'type': 'a' + 'value': '5.6.7.8' + 'answer_dnssec': false + 'client': + 'blocked': false + 'ids': + - '1.2.3.4' + - 'user-1' + 'name': 'User 1' + 'num': 100 + 'num_blocked': 50 + 'uid': 'abcd1234' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + 'elapsed': 3.2 + 'proto': 'udp' + 'question': + 'class': 'in' + 'host': 'example.com' + 'type': 'a' + 'rcode': 'noerror' + 'reason': 'not_filtered_notfound' + 'rules': [] + 'start': 1614345496000 + 'upstream': '8.8.8.8' + 'properties': + 'results': + 'description': > + The query log. + 'items': + '$ref': '#/components/schemas/LogRecord' + 'type': 'array' + 'required': + - 'results' + 'type': 'object' + + 'GetV1ProtectionBlockedServicesResp': + '$ref': '#/components/schemas/BlockedServices' + + 'GetV1ProtectionCustomRulesResp': + '$ref': '#/components/schemas/CustomRules' + + 'GetV1ProtectionDnsRewritesResp': + 'description': > + Classic DNS rewrites. + 'example': + 'rules': + - 'answer': 'A' + 'domain': 'example.com' + 'id': 'abcd1234' + - 'answer': '0.0.0.0' + 'domain': '*.example.org' + 'id': 'efgh5678' + - 'answer': 'my.example.net' + 'domain': 'example.net' + 'id': 'ijkl9012' + 'properties': + 'rules': + 'description': > + All classic DNS rewrites. + 'items': + '$ref': '#/components/schemas/DnsRewrite' + 'type': 'array' + 'required': + - 'rules' + 'type': 'object' + + 'GetV1ProtectionFiltersResp': + 'description': > + Filters. + 'example': + 'filters': + - 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + - 'allowlist': false + 'enabled': true + 'name': 'My personal list' + 'num_rules': 0 + 'refreshed': 1614345497000 + 'uid': 'efgh5678' + 'url': 'file:///home/user/Documents/ad_list.txt' + 'properties': + 'filters': + 'description': > + All current filters. + 'items': + '$ref': '#/components/schemas/Filter' + 'type': 'array' + 'required': + - 'filters' + 'type': 'object' + + # Perhaps a lot of these belong in separate APIs, but our colleagues asked + # to pack as much data into every request as reasonably possible. + 'GetV1SettingsAllResp': + 'description': > + Most settings. + # Don't add examples, as are provided by the subclasses. + 'properties': + 'dhcp': + '$ref': '#/components/schemas/DhcpSettings' + 'dns': + '$ref': '#/components/schemas/DnsSettings' + 'http': + '$ref': '#/components/schemas/HttpSettings' + 'log': + '$ref': '#/components/schemas/LogSettings' + 'protection': + '$ref': '#/components/schemas/ProtectionSettings' + 'stats': + '$ref': '#/components/schemas/StatsSettings' + 'tls': + '$ref': '#/components/schemas/TlsSettings' + 'required': + - 'dhcp' + - 'dns' + - 'http' + - 'log' + - 'protection' + - 'stats' + - 'tls' + 'type': 'object' + + 'GetV1SettingsDnsAccessResp': + '$ref': '#/components/schemas/DnsAccessSettings' + + # See the comment on the GetV1SettingsAllResp schema. + 'GetV1StatsAllResp': + 'description': > + All statistics. + 'example': + 'dns_cache_hit_rate': 56.7 + 'dns_cache_records': 123 + 'graph_avg_processing': + - 3.0 + - 0.4 + 'graph_blocked_ad_queries': + - 10 + - 20 + 'graph_blocked_custom_rule_queries': + - 10 + - 20 + 'graph_blocked_domains': + - 10 + - 20 + 'graph_blocked_parental_control_queries': + - 10 + - 20 + 'graph_blocked_safe_browsing_queries': + - 10 + - 20 + 'graph_blocked_safe_search_queries': + - 10 + - 20 + 'graph_blocked_service_queries': + - 10 + - 20 + 'graph_blocked_tracker_queries': + - 10 + - 20 + 'graph_cpu_percent': + - 50 + - 75 + 'graph_domains': + - 20 + - 30 + 'graph_queries': + - 1000 + - 2002 + 'graph_ram_resident': + - 1048576 + - 2097152 + 'time_unit': 'hour' + 'top_blocked_domains': + - 'name': 'example.net' + 'num': 100 + 'top_clients': + - 'blocked': false + 'ids': + - '1.2.3.4' + - 'user-1' + 'name': 'User 1' + 'num': 100 + 'num_blocked': 50 + 'uid': 'abcd1234' + 'whois': + 'city': 'Minsk' + 'country': 'BY' + - 'blocked': true + 'ids': + - '5.6.7.8' + 'num': 100 + 'num_blocked': 100 + 'top_domains': + - 'name': 'example.com' + 'num': 1000 + - 'name': 'example.net' + 'num': 100 + 'total_blocked_ad_queries': 100 + 'total_blocked_custom_rule_queries': 10 + 'total_blocked_domains': 500 + 'total_blocked_parental_control_queries': 10 + 'total_blocked_safe_browsing_queries': 10 + 'total_blocked_safe_search_queries': 10 + 'total_blocked_service_queries': 10 + 'total_blocked_tracker_queries': 10 + 'total_domains': 1000 + 'total_queries': 10000 + 'properties': + 'dns_cache_hit_rate': + 'description': > + DNS cache hit rate, in percent. + 'maximum': 100.0 + 'minimum': 0.0 + 'format': 'double' + 'type': 'number' + 'dns_cache_records': + 'description': > + Number of DNS responses currently in cache. + 'minimum': 0 + 'format': 'int64' + 'type': 'integer' + 'graph_avg_processing': + 'description': > + Average DNS query processing duration graph information. Each item + is one `time_unit` long. The duration is in milliseconds. Sorted + by time in descending order. + 'items': + 'format': 'double' + 'type': 'number' + 'type': 'array' + 'graph_blocked_ad_queries': + 'description': > + Number of queries blocked by advertising filters graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_custom_rule_queries': + 'description': > + Number of queries blocked by custom filtering rules graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_domains': + 'description': > + Blocked queried domains graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_parental_control_queries': + 'description': > + Number of queries blocked by parental control services graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_safe_browsing_queries': + 'description': > + Number of queries blocked by safe browsing services graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_safe_search_queries': + 'description': > + Number of queries blocked by safe search services graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_service_queries': + 'description': > + Number of queries blocked by blocked service settings graph + information. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_blocked_tracker_queries': + 'description': > + Number of queries blocked by tracker filters graph information. + Each item is one `time_unit` long. Sorted by time in descending + order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_cpu_percent': + 'description': > + CPU usage percentage graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'double' + 'type': 'number' + 'type': 'array' + 'graph_domains': + 'description': > + Queried domains graph information. Each item is one `time_unit` + long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_queries': + 'description': > + Number of served DNS queries graph information. Each item is one + `time_unit` long. Sorted by time in descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'graph_ram_resident': + 'description': > + AdGuard Home's resident memory usage graph information. The size is + in bytes. Each item is one `time_unit` long. Sorted by time in + descending order. + 'items': + 'format': 'int64' + 'type': 'integer' + 'type': 'array' + 'time_unit': + '$ref': '#/components/schemas/TimeUnit' + 'top_blocked_domains': + 'description': > + Top blocked queried domains. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem' + 'type': 'array' + 'top_clients': + 'description': > + Top clients. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/ClientInfo' + 'type': 'array' + 'top_domains': + 'description': > + Top queried domains. Sorted by number in descending order. + 'items': + '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem' + 'type': 'array' + 'total_blocked_ad_queries': + 'description': > + Total number of queries blocked by advertising filters. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_custom_rule_queries': + 'description': > + Total number of queries blocked by custom filtering rules. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_domains': + 'description': > + Total number of blocked queried domains. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_parental_control_queries': + 'description': > + Total number of queries blocked by parental control services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_safe_browsing_queries': + 'description': > + Total number of queries blocked by safe browsing services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_safe_search_queries': + 'description': > + Total number of queries blocked by safe search services. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_service_queries': + 'description': > + Total number of queries blocked by blocked service settings. + 'format': 'int64' + 'type': 'integer' + 'total_blocked_tracker_queries': + 'description': > + Total number of queries blocked by tracker filters. + 'format': 'int64' + 'type': 'integer' + 'total_domains': + 'description': > + Total number of queried domains. + 'format': 'int64' + 'type': 'integer' + 'total_queries': + 'description': > + Total number of served DNS queries. + 'format': 'int64' + 'type': 'integer' + 'required': + - 'dns_cache_hit_rate' + - 'dns_cache_records' + - 'graph_avg_processing' + - 'graph_blocked_ad_queries' + - 'graph_blocked_custom_rule_queries' + - 'graph_blocked_domains' + - 'graph_blocked_parental_control_queries' + - 'graph_blocked_safe_browsing_queries' + - 'graph_blocked_safe_search_queries' + - 'graph_blocked_service_queries' + - 'graph_blocked_tracker_queries' + - 'graph_cpu_percent' + - 'graph_domains' + - 'graph_queries' + - 'graph_ram_resident' + - 'time_unit' + - 'top_blocked_domains' + - 'top_clients' + - 'top_domains' + - 'total_blocked_ad_queries' + - 'total_blocked_custom_rule_queries' + - 'total_blocked_domains' + - 'total_blocked_parental_control_queries' + - 'total_blocked_safe_browsing_queries' + - 'total_blocked_safe_search_queries' + - 'total_blocked_service_queries' + - 'total_blocked_tracker_queries' + - 'total_domains' + - 'total_queries' + 'type': 'object' + + 'GetV1StatsAllRespTopsItem': + 'description': > + A top array item. + 'properties': + 'name': + 'description': > + The name of the entity. Mostly domain names. + 'example': 'example.com' + 'type': 'string' + 'num': + 'description': > + The value of the statistic. + 'example': 1000 + 'format': 'int64' + 'type': 'integer' + 'required': + - 'name' + - 'num' + 'type': 'object' + + 'GetV1SystemInfoResp': + 'description': > + Information about the AdGuard Home server. + 'example': + 'arch': 'amd64' + 'channel': 'release' + 'new_version': 'v0.108.1' + 'os': 'linux' + 'start': 1614345496000 + 'version': 'v0.108.0' + 'properties': + 'arch': + 'description': > + CPU architecture. + 'type': 'string' + 'channel': + '$ref': '#/components/schemas/Channel' + 'new_version': + 'description': > + 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. + 'format': 'double' + 'type': 'number' + 'version': + 'description': > + 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' + 'msg': >- + runtime error: invalid memory address or nil pointer dereference + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'Lang': + 'description': > + Language code. + # Hold the enum in sync with .twosky.json. + 'enum': + - 'be' + - 'bg' + - 'cs' + - 'da' + - 'de' + - 'en' + - 'es' + - 'fa' + - 'fr' + - 'hr' + - 'hu' + - 'id' + - 'it' + - 'ja' + - 'ko' + - 'nl' + - 'no' + - 'pl' + - 'pt-br' + - 'pt-pt' + - 'ro' + - 'ru' + - 'si-lk' + - 'sk' + - 'sl' + - 'sr-cs' + - 'sv' + - 'th' + - 'tr' + - 'vi' + - 'zh-cn' + - 'zh-hk' + - 'zh-tw' + 'type': 'string' + + 'LogRecord': + 'description': > + Query log record. + 'properties': + 'answer': + 'description': > + The answer given to the user. + 'items': + '$ref': '#/components/schemas/LogRecordDnsAnswer' + 'type': 'array' + 'answer_dnssec': + 'description': > + If `true`, DNSSEC was used. + 'type': 'boolean' + 'blocked_service': + 'description': > + Set if `reason` is `filtered_blocked_service`. Otherwise, this + field is absent. + 'type': 'string' + 'client': + '$ref': '#/components/schemas/ClientInfo' + 'elapsed': + 'description': > + Time it took to process the request, in milliseconds. + 'format': 'double' + 'type': 'number' + 'original_answer': + 'description': > + Original answer from the upstream server, if the answer was + rewritten. + 'items': + '$ref': '#/components/schemas/LogRecordDnsAnswer' + 'type': 'array' + 'proto': + '$ref': '#/components/schemas/DnsProto' + 'question': + '$ref': '#/components/schemas/LogRecordDnsQuestion' + 'rcode': + '$ref': '#/components/schemas/DnsResponseCode' + 'reason': + '$ref': '#/components/schemas/FilteringReason' + 'rules': + 'description': > + Applied rules. + 'items': + '$ref': '#/components/schemas/FilteringResultRule' + 'type': 'array' + 'start': + 'description': > + Request processing start Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'upstream': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'required': + - 'answer' + - 'answer_dnssec' + - 'client' + - 'elapsed' + - 'proto' + - 'question' + - 'rcode' + - 'reason' + - 'rules' + - 'start' + - 'upstream' + 'type': 'object' + + 'LogRecordDnsAnswer': + 'description': > + DNS answer section. + 'properties': + 'ttl': + 'description': > + TTL of a record. This value is in **seconds**, like in DNS record + headers. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'type': + '$ref': '#/components/schemas/DnsType' + 'value': + 'description': > + An opaque string describing the result value. + 'type': 'string' + 'required': + - 'ttl' + - 'type' + - 'value' + 'type': 'object' + + 'LogRecordDnsQuestion': + 'description': > + DNS question section. + 'properties': + 'class': + '$ref': '#/components/schemas/DnsClass' + 'host': + 'description': > + Host from the query. + 'type': 'string' + 'type': + '$ref': '#/components/schemas/DnsType' + 'required': + - 'class' + - 'host' + - 'type' + 'type': 'object' + + 'LogSettings': + 'allOf': + - '$ref': '#/components/schemas/LogSettingsPatch' + - 'description': > + Query logging settings. + 'example': + 'anonymize': true + 'enabled': true + 'rotation': 604800000 + 'required': + - 'anonymize' + - 'enabled' + - 'rotation' + + 'LogSettingsPatch': + 'description': > + Query logging settings update object. + 'properties': + 'anonymize': + 'description': > + If `true`, client IP address anonymization is enabled. + 'type': 'boolean' + 'enabled': + 'description': > + If `true`, query logging is enabled. + 'type': 'boolean' + 'rotation': + 'description': > + Log rotation interval, in milliseconds. After that time, the log + file will be replaced by a new one, while the old one gets renamed. + 'format': 'double' + 'minimum': 86400000 + 'maximum': 7776000000 + 'type': 'number' + 'type': 'object' + + 'NetworkInterface': + 'properties': + 'ips': + 'description': > + The IP addresses of the interface, if any. + 'items': + 'type': 'string' + 'type': 'array' + 'mac': + 'description': > + The MAC address of the interface. + 'type': 'string' + 'mtu': + 'description': > + The interface's MTU, the maximum transmission unit. + 'format': 'int64' + 'type': 'integer' + 'name': + 'description': > + The name of the interface. + 'type': 'string' + 'up': + 'description': > + If `true`, the interface is up. + 'type': 'boolean' + 'required': + - 'ips' + - 'mac' + - 'mtu' + - 'name' + - 'up' + 'type': 'object' + + 'NotFoundResp': + 'example': + 'code': 'ENT404' + 'msg': >- + entity not found + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'PatchV1AccountsProfileReq': + 'example': + 'lang': 'ru' + 'properties': + 'lang': + '$ref': '#/components/schemas/Lang' + 'type': 'object' + + 'PatchV1AccountsProfileResp': + '$ref': '#/components/schemas/Profile' + + 'PatchV1ClientPersistentReq': + '$ref': '#/components/schemas/PersistentClientPatch' + + 'PatchV1ClientPersistentResp': + '$ref': '#/components/schemas/PersistentClient' + + 'PatchV1DhcpLeaseReq': + '$ref': '#/components/schemas/DhcpLeasePatch' + + 'PatchV1DhcpLeaseResp': + '$ref': '#/components/schemas/DhcpLease' + + 'PatchV1ProtectionFilterReq': + '$ref': '#/components/schemas/FilterPatch' + + 'PatchV1ProtectionFilterResp': + '$ref': '#/components/schemas/Filter' + + 'PatchV1SettingsDhcpReq': + '$ref': '#/components/schemas/DhcpSettingsPatch' + + 'PatchV1SettingsDhcpResp': + '$ref': '#/components/schemas/DhcpSettings' + + 'PatchV1SettingsDnsReq': + '$ref': '#/components/schemas/DnsSettingsPatch' + + 'PatchV1SettingsDnsResp': + '$ref': '#/components/schemas/DnsSettings' + + 'PatchV1SettingsHttpReq': + '$ref': '#/components/schemas/HttpSettingsPatch' + + 'PatchV1SettingsHttpResp': + '$ref': '#/components/schemas/HttpSettings' + + 'PatchV1SettingsLogReq': + '$ref': '#/components/schemas/LogSettingsPatch' + + 'PatchV1SettingsLogResp': + '$ref': '#/components/schemas/LogSettings' + + 'PatchV1SettingsProtectionReq': + '$ref': '#/components/schemas/ProtectionSettingsPatch' + + 'PatchV1SettingsProtectionResp': + '$ref': '#/components/schemas/ProtectionSettings' + + 'PatchV1SettingsStatsReq': + '$ref': '#/components/schemas/StatsSettingsPatch' + + 'PatchV1SettingsStatsResp': + '$ref': '#/components/schemas/StatsSettings' + + 'PatchV1SettingsTlsReq': + '$ref': '#/components/schemas/TlsSettingsPatch' + + 'PatchV1SettingsTlsResp': + '$ref': '#/components/schemas/TlsSettings' + + 'PersistentClient': + 'allOf': + - '$ref': '#/components/schemas/PersistentClientPatch' + - 'description': > + Persistent client. + 'example': + 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'num_blocked_requests': 50 + 'num_requests': 100 + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'uid': 'abcd1234' + 'upstream_servers': [] + 'properties': + 'num_blocked_requests': + 'description': > + Total number of blocked requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'num_requests': + 'description': > + Total number of requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'blocked' + - 'blocked_services' + - 'filtering' + - 'ids' + - 'name' + - 'parental' + - 'safe_browsing' + - 'safe_search' + - 'tags' + - 'uid' + - 'upstream_servers' + - 'use_global_blocked_services' + - 'use_global_settings' + + 'PersistentClientPatch': + 'description': > + Persistent client update object. + 'example': + 'filtering': false + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'properties': + 'blocked': + 'description': > + If `true`, the client is blocked. + 'type': 'boolean' + 'blocked_services': + 'description': > + Custom blocked services for this client. + 'items': + '$ref': '#/components/schemas/BlockedServiceId' + 'type': 'array' + 'filtering': + 'description': > + If `true`, filtering based on filter rule lists is enabled for this + client. + 'type': 'boolean' + 'ids': + 'description': > + IP, CIDR, MAC, or ClientID (not to be confused with the `uid` field) + for client identification. + 'items': + 'type': 'string' + 'type': 'array' + 'name': + 'description': > + The name of this client. + 'type': 'string' + 'parental': + 'description': > + If `true`, parental protection is enabled for this client. + 'type': 'boolean' + 'safe_browsing': + 'description': > + If `true`, safe browsing protection is enabled for this client. + 'type': 'boolean' + 'safe_search': + 'description': > + If `true`, safe search protection is enabled for this client. + 'type': 'boolean' + 'tags': + 'description': > + Client tags. + 'items': + '$ref': '#/components/schemas/PersistentClientTag' + 'type': 'array' + 'use_global_blocked_services': + 'description': > + If `true`, use global blocked services for this client instead of + the custom ones. + 'type': 'boolean' + 'use_global_settings': + 'description': > + If `true`, use global protection settings for this client instead of + the custom ones. + 'type': 'boolean' + 'upstream_servers': + 'description': > + Custom upstream DNS servers for this client. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'type': 'object' + + 'PersistentClientPost': + 'allOf': + - '$ref': '#/components/schemas/PersistentClientPatch' + - 'description': > + Persistent client create object. + 'example': + 'blocked': false + 'blocked_services': [] + 'filtering': false + 'ids': ['client-1'] + 'name': 'Client 1' + 'parental': false + 'safe_browsing': false + 'safe_search': false + 'tags': ['user_admin'] + 'use_global_blocked_services': true + 'use_global_settings': true + 'upstream_servers': [] + 'required': + - 'blocked' + - 'blocked_services' + - 'filtering' + - 'ids' + - 'name' + - 'parental' + - 'safe_browsing' + - 'safe_search' + - 'tags' + - 'upstream_servers' + - 'use_global_blocked_services' + - 'use_global_settings' + + 'PersistentClientTag': + 'description': > + Tags can be included in filtering rules to allow you to apply them more + accurately. + 'enum': + - 'device_audio' + - 'device_camera' + - 'device_gameconsole' + - 'device_laptop' + - 'device_nas' + - 'device_other' + - 'device_pc' + - 'device_phone' + - 'device_printer' + - 'device_securityalarm' + - 'device_tablet' + - 'device_tv' + - 'os_android' + - 'os_ios' + - 'os_linux' + - 'os_macos' + - 'os_other' + - 'os_windows' + - 'user_admin' + - 'user_child' + - 'user_regular' + 'type': 'string' + + 'PostV1AccountsSessionReq': + 'example': + 'password': 'G00dp455word!' + 'username': 'admin' + 'properties': + 'password': + 'description': > + Password. + 'format': 'password' + 'type': 'string' + 'username': + 'description': > + Username. + 'type': 'string' + 'required': + - 'password' + - 'username' + 'type': 'object' + + 'PostV1ClientsPersistentReq': + '$ref': '#/components/schemas/PersistentClientPost' + + 'PostV1ClientsPersistentResp': + '$ref': '#/components/schemas/PersistentClient' + + 'PostV1DhcpLeasesReq': + '$ref': '#/components/schemas/DhcpLeasePost' + + 'PostV1DhcpLeasesResp': + '$ref': '#/components/schemas/DhcpLease' + + 'PostV1InstallCheckReq': + 'description': > + Configuration for checking. + 'example': + 'dns': + 'ip': + - '0.0.0.0' + 'port': 53 + 'static_ip': false + 'web': + 'ip': + - '0.0.0.0' + 'port': 80 + 'properties': + 'dns': + '$ref': '#/components/schemas/PostV1InstallCheckReqServer' + 'static_ip': + 'description': > + If `true`, check if a static IP is set or can be set. + 'type': 'boolean' + 'web': + '$ref': '#/components/schemas/PostV1InstallCheckReqServer' + 'required': + - 'dns' + - 'static_ip' + - 'web' + 'type': 'object' + + 'PostV1InstallCheckReqServer': + 'description': > + A configuration for a server check. + 'properties': + 'ip': + 'description': > + IP addresses to check for availability. + 'items': + 'type': 'string' + 'minItems': 1 + 'type': 'array' + 'port': + 'description': > + Port to check for availability. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'ip' + - 'port' + 'type': 'object' + + 'PostV1InstallCheckResp': + 'description': > + Configuration checking response. + 'example': + 'dns': + 'error': 'permission denied' + 'static_ip': + 'ip': '192.168.1.1' + 'static': true + 'supported': true + 'web': {} + 'properties': + 'dns': + '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork' + 'static_ip': + '$ref': '#/components/schemas/StaticIpCheckResult' + 'web': + '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork' + 'required': + - 'dns' + - 'static_ip' + - 'web' + 'type': 'object' + + 'PostV1InstallCheckRespNetwork': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'type': 'object' + + 'PostV1InstallConfigureReq': + 'description': > + AdGuard Home initial configuration. + 'example': + 'dns_ip': '0.0.0.0' + 'dns_port': 53 + 'password': 'G00dp455word!' + 'username': 'admin' + 'set_static_ip': true + 'web_ip': '0.0.0.0' + 'web_port': 80 + 'properties': + 'dns_ip': + 'description': > + The IP address to serve DNS queries on. + 'type': 'string' + 'dns_port': + 'description': > + The port to serve DNS queries on. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'password': + 'description': > + Password. + 'type': 'string' + 'username': + 'description': > + Username. + 'type': 'string' + 'set_static_ip': + 'description': > + If `true`, set the server's IP address to static. + 'type': 'boolean' + 'web_ip': + 'description': > + The IP address to serve the web interface on. + 'type': 'string' + 'web_port': + 'description': > + The port to serve the web interface on. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'required': + - 'dns_ip' + - 'dns_port' + - 'password' + - 'username' + - 'set_static_ip' + - 'web_ip' + - 'web_port' + 'type': 'object' + + 'PostV1LogClearReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1ProtectionCheckCustomRulesReq': + 'description': > + Data to check using custom filtering rules. + 'example': + 'host': 'example.com' + 'properties': + 'host': + 'description': > + The hostname to check. + 'type': 'string' + 'required': + - 'host' + 'type': 'object' + + 'PostV1ProtectionCheckCustomRulesResp': + 'description': > + Custom filtering rules check results. + 'example': + 'reason': 'filtered_blocklist' + 'rules': + - 'filter_list_uid': 'abcd1234' + 'text': '||example.com^' + 'properties': + 'cname': + 'description': > + Set if `reason` is `Rewrite`. Otherwise, this field is absent. + 'type': 'string' + 'ip_addrs': + 'description': > + Set if `reason` is `Rewrite`. Otherwise, this field is absent. + 'items': + 'type': 'string' + 'type': 'array' + 'reason': + '$ref': '#/components/schemas/FilteringReason' + 'rules': + 'description': > + Applied rules. + 'items': + '$ref': '#/components/schemas/FilteringResultRule' + 'type': 'array' + 'service_name': + 'description': > + Set if `reason` is `FilteredBlockedService`. Otherwise, this field + is absent. + 'type': 'string' + 'required': + - 'reason' + - 'rules' + 'type': 'object' + + 'PostV1ProtectionDnsRewritesReq': + '$ref': '#/components/schemas/DnsRewritePost' + + 'PostV1ProtectionDnsRewritesResp': + '$ref': '#/components/schemas/DnsRewrite' + + 'PostV1ProtectionFiltersReq': + '$ref': '#/components/schemas/FilterPost' + + 'PostV1ProtectionFiltersResp': + '$ref': '#/components/schemas/Filter' + + 'PostV1ProtectionRefreshFilterReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1ProtectionRefreshFilterResp': + '$ref': '#/components/schemas/Filter' + + 'PostV1ProtectionRefreshFiltersReq': + 'description': > + Filters refresh parameters. + 'example': + 'allowlist': false + 'blocklist': true + 'properties': + 'allowlist': + 'description': > + If `true`, refresh all allowlist filters. + 'type': 'boolean' + 'blocklist': + 'description': > + If `true`, refresh all blocklist filters. + 'type': 'boolean' + 'required': + - 'allowlist' + - 'blocklist' + 'type': 'object' + + 'PostV1ProtectionRefreshFiltersResp': + 'description': > + Refresh results. + 'example': + 'errors': + - 'msg': 'context deadline exceeded' + 'uid': 'efgh5678' + 'refreshed': + - 'allowlist': false + 'enabled': true + 'name': 'AdMaster 5000 Super List v2.0 Final' + 'num_rules': 36766 + 'refreshed': 1614345496000 + 'uid': 'abcd1234' + 'url': 'https://admaster.example.com/list.txt' + 'properties': + 'errors': + 'description': > + All encountered errors. + 'items': + '$ref': '#/components/schemas/RefreshFilterError' + 'type': 'array' + 'refreshed': + 'description': > + Refreshed filters. + 'items': + '$ref': '#/components/schemas/Filter' + 'type': 'array' + 'required': + - 'errors' + - 'refreshed' + 'type': 'object' + + 'PostV1SettingsDnsCheckReq': + 'description': > + Validatable DNS settings. + 'example': + 'bootstrap_servers': + - '9.9.9.10' + - '149.112.112.10' + 'upstream_servers': + - '1.1.1.1' + - '8.8.8.8' + 'properties': + 'bootstrap_servers': + 'description': | + Bootstrap DNS servers' IP addresses to check. + 'items': + 'type': 'string' + 'type': 'array' + 'upstream_servers': + 'description': > + Upstream DNS servers to check. + 'items': + '$ref': '#/components/schemas/UpstreamServerAddr' + 'type': 'array' + 'required': + - 'bootstrap_servers' + - 'upstream_servers' + 'type': 'object' + + 'PostV1SettingsDnsCheckResp': + 'description': > + DNS settings validation results. + 'example': + 'bootstrap_servers': + '9.9.9.10': 'network is unreachable' + 'upstream_servers': + '8.8.8.8': 'network is unreachable' + 'properties': + 'bootstrap_servers': + 'additionalProperties': + 'minLength': 1 + 'type': 'string' + 'description': > + An IP-address-to-error mapping. If an address is not in this + object, the check for that address is successful. If there were no + errors, this field is absent. + 'upstream_servers': + 'additionalProperties': + 'type': 'string' + 'description': > + An upstream-address-to-error mapping. If an address is not in this + object, the check for that address is successful. If there were no + errors, this field is absent. + 'type': 'object' + + 'PostV1SettingsTlsCheckReq': + 'description': > + Validatable TLS settings. + 'example': + 'certificate_path': '/etc/ssl/example.com.cert' + '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' + 'properties': + 'certificate': + 'description': | + Base64-encoded string with PEM-encoded certificate chain. + + Should not be sent if `certificate_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'certificate_path': + 'description': | + Path to the certificate file. + + Should not be sent if `certificate` is sent. Otherwise, must be + sent. + 'type': 'string' + 'port_dns_over_quic': + 'default': 853 + 'description': > + The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_dns_over_tls': + 'default': 853 + 'description': > + The DNS-over-TLS port. If `0`, DNS-over-TLS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_https': + 'default': 443 + 'description': > + The HTTPS port. If `0`, HTTPS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'private_key': + 'description': | + Base64-encoded string with PEM-encoded private key. + + Should not be sent if `private_key_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'private_key_path': + 'description': | + Path to the private key file. + + Should not be sent if `private_key` is sent. Otherwise, must be + sent. + 'type': 'string' + 'server_name': + 'description': > + The name of the server. Used to validate the certificates as well + as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS. + 'type': 'string' + 'required': + - 'port_dns_over_quic' + - 'port_dns_over_tls' + - 'port_https' + - 'server_name' + 'type': 'object' + + 'PostV1SettingsTlsCheckResp': + 'description': > + TLS settings validation results. + 'example': + 'dns_names': + - '*.example.com' + - 'example.com' + 'issuer': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU' + 'key_type': 'RSA' + 'not_after': 1614345497000 + 'not_before': 1614345496000 + 'port_https_error': 'address already in use' + 'subject': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU' + 'warnings': [] + 'properties': + 'cert_error': + 'description': > + Certificate validation error, if any. If the certificate is valid, + this field is absent. + 'type': 'string' + 'chain_error': + 'description': > + Certificate chain validation error, if any. If the certificate + chain is valid, this field is absent. + 'type': 'string' + 'dns_names': + 'description': > + The value of the `SubjectAltNames` field of the first certificate in + the chain. + 'items': + 'type': 'string' + 'type': 'array' + 'issuer': + 'description': > + The issuer of the first certificate in the chain. + 'type': 'string' + 'key_error': + 'description': > + Private key pair error, if any. If the key is valid, this field is + absent. + 'type': 'string' + 'key_type': + '$ref': '#/components/schemas/TlsKeyType' + 'not_after': + 'description': > + The value of the `NotAfter` field of the first certificate in the + chain, as a Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'not_before': + 'description': > + The value of the `NotBefore` field of the first certificate in the + chain, as a Unix time, in milliseconds. + 'format': 'double' + 'type': 'number' + 'port_dns_over_quic_error': + 'description': > + DNS-over-QUIC port checking error, if any. If the port is + available, this field is absent. + 'type': 'string' + 'port_dns_over_tls_error': + 'description': > + DNS-over-TLS port checking error, if any. If the port is available, + this field is absent. + 'type': 'string' + 'port_https_error': + 'description': > + DNS-over-HTTPS port checking error, if any. If the port is + available, this field is absent. + 'type': 'string' + 'pair_error': + 'description': > + Certificate and key pair error, if any. If the pair is valid, this + field is absent. + 'type': 'string' + 'subject': + 'description': > + The subject of the first certificate in the chain. + 'type': 'string' + 'warnings': + 'description': > + Validation warnings, if any. + 'items': + 'type': 'string' + 'type': 'array' + 'required': + - 'dns_names' + - 'issuer' + - 'key_type' + - 'not_after' + - 'not_before' + - 'subject' + - 'warnings' + 'type': 'object' + + 'PostV1StatsClearReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemResetReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemResetResp': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemUpdateReq': + 'description': > + Currently empty, may get more fields in the future. + 'type': 'object' + + 'PostV1SystemUpdateResp': + 'example': + 'reload': 10000 + 'properties': + 'reload': + 'description': > + Time, after which the frontend must reload the page, in + milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'Profile': + 'description': > + Current user's profile. + 'example': + 'lang': 'en' + 'username': 'admin' + 'properties': + 'lang': + '$ref': '#/components/schemas/Lang' + 'username': + 'description': > + Current user's name. + 'type': 'string' + 'required': + - 'lang' + - 'username' + 'type': 'object' + + 'ProtectionSettings': + 'allOf': + - '$ref': '#/components/schemas/ProtectionSettingsPatch' + - 'description': > + Protection settings. + 'example': + 'autoupdate': 86400000 + 'filtering': true + 'parental': true + 'safe_browsing': false + 'safe_search': false + 'required': + - 'autoupdate' + - 'filtering' + - 'parental' + - 'safe_browsing' + - 'safe_search' + + 'ProtectionSettingsPatch': + 'description': > + Protection settings update object. + 'example': + 'autoupdate': 0 + 'properties': + 'autoupdate': + 'description': > + Filter automatic update interval, in milliseconds. Set to `0` to + disable automatic updates. + 'format': 'double' + 'minimum': 0 + 'maximum': 604800000 + 'type': 'number' + 'filtering': + 'description': > + If `true`, filtering based on filter rule lists is enabled. + 'type': 'boolean' + 'parental': + 'description': > + If `true`, parental protection is enabled. + 'type': 'boolean' + 'pause_end': + 'description': | + If `state` is `paused`, `pause_end` will show the Unix time until + which the protection is disabled in milliseconds. Otherwise, the + property won't be set. + + When updating, if `state` is set to `paused`, `pause_end` must be + set to a timestamp in the future. + 'format': 'double' + 'type': 'number' + 'safe_browsing': + 'description': > + If `true`, safe browsing protection is enabled. + 'type': 'boolean' + 'safe_search': + 'description': > + If `true`, safe search protection is enabled. + 'type': 'boolean' + 'state': + '$ref': '#/components/schemas/ProtectionSettingsState' + 'type': 'object' + + 'ProtectionSettingsState': + 'description': | + State of protection. + + * `off`: Protection is disabled. + + * `on`: Protection is enabled. + + * `paused`: Protection is paused. See the `pause_end` property to get + or set the end of the pause. + 'enum': + - 'off' + - 'on' + - 'paused' + 'type': 'string' + + 'PutV1ProtectionBlockedServicesReq': + '$ref': '#/components/schemas/BlockedServices' + + 'PutV1ProtectionCustomRulesReq': + '$ref': '#/components/schemas/CustomRules' + + 'PutV1SettingsDnsAccessReq': + '$ref': '#/components/schemas/DnsAccessSettings' + + 'RefreshFilterError': + 'description': > + Filter refresh error. + 'properties': + 'msg': + 'description': > + Error message. + 'type': 'string' + 'uid': + '$ref': '#/components/schemas/Uid' + 'required': + - 'msg' + - 'uid' + 'type': 'object' + + 'RuntimeClient': + 'description': > + A runtime client's information. + 'properties': + 'host': + 'description': > + The RDNS host of the runtime, if any. If there is none, this field + is absent. + 'type': 'string' + 'ip': + 'description': > + The IP-address of the runtime client. + 'type': 'string' + 'sources': + 'description': > + The sources from which the information about this runtime client was + collected. + 'items': + '$ref': '#/components/schemas/RuntimeClientSource' + 'minItems': 1 + 'type': 'array' + 'num_blocked_requests': + 'description': > + Total number of blocked requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'num_requests': + 'description': > + Total number of requests for this runtime client. + 'format': 'int64' + 'minimum': 0 + 'type': 'integer' + 'whois': + '$ref': '#/components/schemas/Whois' + 'required': + - 'ip' + - 'num_blocked_requests' + - 'num_requests' + - 'sources' + 'type': 'object' + + 'RuntimeClientSource': + 'description': > + The source from which the information about this runtime client was + collected. + + * `arp`: The information was collected from the `arp -a` output. + + * `dhcp`: The information was collected from our DHCP server. + + * `hosts_file`: The information was collected from the `/etc/hosts` + file. + + * `rdns`: The information was collected by performing a reverse DNS + lookup. + + * `whois`: The information was collected by performing a WHOIS lookup. + 'enum': + - 'arp' + - 'dhcp' + - 'hosts_file' + - 'rdns' + - 'whois' + 'type': 'string' + + 'StaticIpCheckResult': + 'properties': + 'error': + 'description': > + Error, if any. If there is no error, this field is absent. + 'type': 'string' + 'ip': + 'description': > + The IP address. + 'type': 'string' + 'static': + 'description': > + If `true`, the interface has a static IP address. + 'type': 'boolean' + 'supported': + 'description': > + If `true`, setting a static IP on this system is supported. + 'type': 'boolean' + 'required': + - 'ip' + - 'static' + - 'supported' + 'type': 'object' + + 'StatsSettings': + 'allOf': + - '$ref': '#/components/schemas/StatsSettingsPatch' + - 'description': > + Statistics settings. + 'required': + - 'autorefresh' + - 'retention' + + 'StatsSettingsPatch': + 'description': > + Statistics settings update object. + 'properties': + 'autorefresh': + 'description': > + Statistics UI autorefresh time in milliseconds. `0` means + autorefresh is disabled. + 'format': 'double' + 'type': 'number' + 'retention': + 'description': > + Statistics retention interval, in milliseconds. + 'format': 'double' + 'type': 'number' + 'type': 'object' + + 'TimeUnit': + 'description': > + Time units used for statistics. See the documentation for the + `GET /api/v1/stats/all` request. + 'enum': + - 'hour' + - 'day' + 'type': 'string' + + 'TlsKeyType': + 'description': > + TLS key type. + 'enum': + - 'ECDSA' + - 'RSA' + 'type': 'string' + + 'TlsSettings': + 'allOf': + - '$ref': '#/components/schemas/TlsSettingsPatch' + - 'description': > + TLS and encryption settings. + 'example': + 'certificate_path': '/etc/ssl/example.com.cert' + 'enabled': true + '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' + - 'port_dns_over_quic' + - 'port_dns_over_tls' + - 'port_https' + - 'server_name' + + 'TlsSettingsPatch': + 'description': > + TLS and encryption settings update object. + 'example': + 'certificate': 'Base64KeyDatAA==' + 'enabled': true + 'private_key': 'Base64CertDatA==' + 'properties': + 'certificate': + 'description': | + Base64-encoded string with PEM-encoded certificate chain. + + Should not be sent if `certificate_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'certificate_path': + 'description': | + Path to the certificate file. + + Should not be sent if `certificate` is sent. Otherwise, must be + sent. + 'type': 'string' + 'enabled': + 'description': > + If `true`, AdGuard Home the administration interface will be served + over HTTPS, and the DNS server will listen requests over + DNS-over-TLS and other protocols. + 'type': 'boolean' + 'port_dns_over_quic': + 'default': 853 + 'description': > + The DNS-over-QUIC port. If `0`, DNS-over-QUIC is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_dns_over_tls': + 'default': 853 + 'description': > + The DNS-over-TLS port. If `0`, DNS-over-TLS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'port_https': + 'default': 443 + 'description': > + The HTTPS port. If `0`, HTTPS is disabled. + 'format': 'int64' + 'maximum': 65535 + 'minimum': 0 + 'type': 'integer' + 'private_key': + 'description': | + Base64-encoded string with PEM-encoded private key. + + Should not be sent if `private_key_path` is sent. Otherwise, must + be sent. + 'format': 'byte' + 'type': 'string' + 'private_key_path': + 'description': | + Path to the private key file. + + Should not be sent if `private_key` is sent. Otherwise, must be + sent. + 'type': 'string' + 'server_name': + 'description': > + The name of the server. Used to validate the certificates as well + as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS. + 'type': 'string' + 'type': 'object' + + 'Uid': + 'description': > + A unique ID of an entity, an opaque string. + 'pattern': '[0-9a-zA-Z_-]{1,64}' + 'type': 'string' + + 'UnauthorizedResp': + 'example': + 'code': 'AUT000' + 'msg': 'no or bad authorization provided' + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'UnprocessableEntityResp': + 'example': + 'code': 'JSN001' + 'msg': >- + json: cannot unmarshal string into Go struct field T.A of type int + 'properties': + 'code': + '$ref': '#/components/schemas/ErrorCode' + 'msg': + 'description': > + Error message string. + 'type': 'string' + 'required': + - 'code' + - 'msg' + 'type': 'object' + + 'UpstreamServerAddr': + 'description': | + Upstream DNS server address. Supported item formats: + + * `94.140.14.140`: plain DNS-over-UDP. + + * `tls://unfiltered.adguard-dns.com`: encrypted DNS-over-TLS. + + * `https://unfiltered.adguard-dns.com/dns-query`: encrypted + DNS-over-HTTPS. + + * `quic://unfiltered.adguard-dns.com`: encrypted DNS-over-QUIC. + + * `tcp://94.140.14.140`: plain DNS-over-TCP. + + * `sdns://...`: DNS Stamps for DNSCrypt or DNS-over-HTTPS + resolvers. + + * `[/example.local/]94.140.14.140`: DNS upstream for specific + domain(s). + + * `# comment`: A comment. + 'type': 'string' + + 'Whois': + 'additionalProperties': + 'type': 'string' + 'description': > + WHOIS information, if any. If there are none, this field is usually + absent. + 'minProperties': 1 + 'type': 'object' + + # TODO(a.garipov): Find a way to specify a cookie authorization. + 'securitySchemes': + 'basicAuth': + 'description': > + Basic HTTP authorization. + 'scheme': 'basic' + 'type': 'http' 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-build.sh b/scripts/make/go-build.sh index 7854166c..c998a611 100644 --- a/scripts/make/go-build.sh +++ b/scripts/make/go-build.sh @@ -123,4 +123,14 @@ CGO_ENABLED="$cgo_enabled" GO111MODULE='on' export CGO_ENABLED GO111MODULE -"$go" build --ldflags "$ldflags" "$race_flags" --trimpath "$o_flags" "$v_flags" "$x_flags" +# Build the new binary if requested. +if [ "${V1API:-0}" -eq '0' ] +then + tags_flags='--tags=' +else + tags_flags='--tags=v1' +fi +readonly tags_flags + +"$go" build --ldflags "$ldflags" "$race_flags" "$tags_flags" --trimpath "$o_flags" "$v_flags"\ + "$x_flags" diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 8e646f0c..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. @@ -134,12 +134,13 @@ underscores() { -e '_bsd.go'\ -e '_darwin.go'\ -e '_freebsd.go'\ - -e '_openbsd.go'\ -e '_linux.go'\ -e '_little.go'\ + -e '_openbsd.go'\ -e '_others.go'\ -e '_test.go'\ -e '_unix.go'\ + -e '_v1.go'\ -e '_windows.go' \ -v\ | sed -e 's/./\t\0/' @@ -153,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`? @@ -211,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 ./... @@ -222,7 +226,7 @@ gocyclo --over 17 ./internal/dhcpd/ ./internal/dnsforward/\ # Apply stricter standards to new or somewhat refactored code. gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\ ./internal/aghtest/ ./internal/stats/ ./internal/tools/\ - ./internal/updater/ ./internal/version/ ./main.go + ./internal/updater/ ./internal/v1/ ./internal/version/ ./main.go\ ineffassign ./... 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