Compare commits
39 Commits
v0.105.0-b
...
fix-client
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46698078a0 | ||
|
|
59a3045615 | ||
|
|
1453c27d87 | ||
|
|
e31c0c456a | ||
|
|
836d0db563 | ||
|
|
a0abad6644 | ||
|
|
1122e71cf3 | ||
|
|
e32c18faab | ||
|
|
f68f6c9b37 | ||
|
|
dbcc55f528 | ||
|
|
66b549a565 | ||
|
|
a1c9e9d36b | ||
|
|
ab81ff03f6 | ||
|
|
d295621352 | ||
|
|
aebcd74efe | ||
|
|
7d1ca48ae4 | ||
|
|
8a0bc5468b | ||
|
|
e272e19516 | ||
|
|
10f03b7527 | ||
|
|
0d44822c43 | ||
|
|
e83b919dbd | ||
|
|
2eb21ef409 | ||
|
|
7b014082ab | ||
|
|
6b8a46ef3b | ||
|
|
a623ac694b | ||
|
|
7dd2d0af96 | ||
|
|
7e08565212 | ||
|
|
841bb9bc35 | ||
|
|
f016ae172c | ||
|
|
44168292d5 | ||
|
|
e64df20e74 | ||
|
|
890f0322d8 | ||
|
|
2bf2d5a19d | ||
|
|
771a32cc9d | ||
|
|
9df0935781 | ||
|
|
a3dddd72c1 | ||
|
|
6471504555 | ||
|
|
1fa4d55ae3 | ||
|
|
63e4adc0e7 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,4 +1 @@
|
|||||||
client/* linguist-vendored
|
client/* linguist-vendored
|
||||||
client/src/__locales/*.json binary
|
|
||||||
# Make an exception for the english locale, as it is the base one.
|
|
||||||
client/src/__locales/en.json -binary
|
|
||||||
|
|||||||
12
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,11 +1,9 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a bug report to help us improve AdGuard Home
|
about: Create a bug report to help us improve AdGuard Home
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
|
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
|
### Prerequisites
|
||||||
|
|
||||||
@@ -17,16 +15,18 @@ Please answer the following questions for yourself before submitting an issue. *
|
|||||||
|
|
||||||
### Issue Details
|
### Issue Details
|
||||||
|
|
||||||
<!--- Please include all relevant details about the environment you experienced the bug in -->
|
<!-- Please include all relevant details about the environment you experienced the bug in. -->
|
||||||
|
|
||||||
* **Version of AdGuard Home server:**
|
* **Version of AdGuard Home server:**
|
||||||
* <!-- (e.g. v1.0) -->
|
* <!-- (e.g. v0.123.4) -->
|
||||||
* **How did you install AdGuard Home:**
|
* **How did you install AdGuard Home:**
|
||||||
* <!-- (e.g. Snapcraft, Docker, Github releases) -->
|
* <!-- (e.g. Built from source, Snapcraft, Docker, Github releases, etc.) -->
|
||||||
* **How did you setup DNS configuration:**
|
* **How did you setup DNS configuration:**
|
||||||
* <!-- (System/Router/IoT) -->
|
* <!-- (System/Router/IoT) -->
|
||||||
* **If it's a router or IoT, please write device model:**
|
* **If it's a router or IoT, please write device model:**
|
||||||
* <!-- (e.g. Raspberry Pi 3 Model B) -->
|
* <!-- (e.g. Raspberry Pi 3 Model B) -->
|
||||||
|
* **CPU architecture:**
|
||||||
|
* <!-- (e.g. AMD64, MIPS, etc.) -->
|
||||||
* **Operating system and version:**
|
* **Operating system and version:**
|
||||||
* <!-- (e.g. Ubuntu 18.04.1) -->
|
* <!-- (e.g. Ubuntu 18.04.1) -->
|
||||||
|
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,11 +1,9 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for AdGuard Home
|
about: Suggest a feature request for AdGuard Home
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
|
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
|
### Prerequisites
|
||||||
|
|
||||||
|
|||||||
8
.github/stale.yml
vendored
8
.github/stale.yml
vendored
@@ -1,20 +1,22 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale.
|
# Number of days of inactivity before an issue becomes stale.
|
||||||
'daysUntilStale': 60
|
'daysUntilStale': 90
|
||||||
# Number of days of inactivity before a stale issue is closed.
|
# Number of days of inactivity before a stale issue is closed.
|
||||||
'daysUntilClose': 7
|
'daysUntilClose': 15
|
||||||
# Issues with these labels will never be considered stale.
|
# Issues with these labels will never be considered stale.
|
||||||
'exemptLabels':
|
'exemptLabels':
|
||||||
- 'bug'
|
- 'bug'
|
||||||
- 'enhancement'
|
- 'enhancement'
|
||||||
- 'feature request'
|
- 'feature request'
|
||||||
- 'localization'
|
- 'localization'
|
||||||
|
- 'needs investigation'
|
||||||
- 'recurrent'
|
- 'recurrent'
|
||||||
|
- 'research'
|
||||||
# Label to use when marking an issue as stale.
|
# Label to use when marking an issue as stale.
|
||||||
'staleLabel': 'wontfix'
|
'staleLabel': 'wontfix'
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable.
|
# Comment to post when marking an issue as stale. Set to `false` to disable.
|
||||||
'markComment': >
|
'markComment': >
|
||||||
This issue has been automatically marked as stale because it has not had
|
This issue has been automatically marked as stale because it has not had
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
for your contributions.
|
for your contributions.
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable.
|
# Comment to post when closing a stale issue. Set to `false` to disable.
|
||||||
'closeComment': false
|
'closeComment': false
|
||||||
|
|||||||
76
CHANGELOG.md
76
CHANGELOG.md
@@ -10,9 +10,70 @@ and this project adheres to
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
## [v0.105.0] - 2021-02-08
|
## [v0.106.0] - 2021-04-26
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## [v0.105.2] - 2021-02-24
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- DHCP lease's `expired` field incorrect time format ([#2692]).
|
||||||
|
- Incomplete DNS upstreams validation ([#2674]).
|
||||||
|
- Wrong parsing of DHCP options of the `ip` type ([#2688]).
|
||||||
|
|
||||||
|
[#2674]: https://github.com/AdguardTeam/AdGuardHome/issues/2674
|
||||||
|
[#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688
|
||||||
|
[#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.105.1] - 2021-02-15
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Increased HTTP API timeouts ([#2671], [#2682]).
|
||||||
|
- "Permission denied" errors when checking if the machine has a static IP no
|
||||||
|
longer prevent the DHCP server from starting ([#2667]).
|
||||||
|
- The server name sent by clients of TLS APIs is not only checked when
|
||||||
|
`strict_sni_check` is enabled ([#2664]).
|
||||||
|
- HTTP API request body size limit for the `POST /control/access/set` and `POST
|
||||||
|
/control/filtering/set_rules` HTTP APIs is increased ([#2666], [#2675]).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Error when enabling the DHCP server when AdGuard Home couldn't determine if
|
||||||
|
the machine has a static IP.
|
||||||
|
- Optical issue on custom rules ([#2641]).
|
||||||
|
- 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 any more ([#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.
|
||||||
|
- Incorrect version tag in the Docker release ([#2663]).
|
||||||
|
- DNSCrypt queries weren't marked as such in logs ([#2662]).
|
||||||
|
|
||||||
|
[#2641]: https://github.com/AdguardTeam/AdGuardHome/issues/2641
|
||||||
|
[#2653]: https://github.com/AdguardTeam/AdGuardHome/issues/2653
|
||||||
|
[#2658]: https://github.com/AdguardTeam/AdGuardHome/issues/2658
|
||||||
|
[#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662
|
||||||
|
[#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663
|
||||||
|
[#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664
|
||||||
|
[#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666
|
||||||
|
[#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667
|
||||||
|
[#2671]: https://github.com/AdguardTeam/AdGuardHome/issues/2671
|
||||||
|
[#2675]: https://github.com/AdguardTeam/AdGuardHome/issues/2675
|
||||||
|
[#2678]: https://github.com/AdguardTeam/AdGuardHome/issues/2678
|
||||||
|
[#2682]: https://github.com/AdguardTeam/AdGuardHome/issues/2682
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.105.0] - 2021-02-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added more services to the "Blocked services" list ([#2224], [#2401]).
|
- Added more services to the "Blocked services" list ([#2224], [#2401]).
|
||||||
@@ -47,7 +108,7 @@ and this project adheres to
|
|||||||
- Improved HTTP requests handling and timeouts ([#2343]).
|
- Improved HTTP requests handling and timeouts ([#2343]).
|
||||||
- Our snap package now uses the `core20` image as its base ([#2306]).
|
- Our snap package now uses the `core20` image as its base ([#2306]).
|
||||||
- New build system and various internal improvements ([#2271], [#2276], [#2297],
|
- New build system and various internal improvements ([#2271], [#2276], [#2297],
|
||||||
[#2509], [#2552]).
|
[#2509], [#2552], [#2639], [#2646]).
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
@@ -107,6 +168,8 @@ and this project adheres to
|
|||||||
[#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552
|
[#2552]: https://github.com/AdguardTeam/AdGuardHome/issues/2552
|
||||||
[#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589
|
[#2589]: https://github.com/AdguardTeam/AdGuardHome/issues/2589
|
||||||
[#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630
|
[#2630]: https://github.com/AdguardTeam/AdGuardHome/issues/2630
|
||||||
|
[#2639]: https://github.com/AdguardTeam/AdGuardHome/issues/2639
|
||||||
|
[#2646]: https://github.com/AdguardTeam/AdGuardHome/issues/2646
|
||||||
|
|
||||||
## [v0.104.3] - 2020-11-19
|
## [v0.104.3] - 2020-11-19
|
||||||
|
|
||||||
@@ -147,9 +210,12 @@ and this project adheres to
|
|||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.2...HEAD
|
||||||
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
[v0.105.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...v0.105.2
|
||||||
-->
|
-->
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD
|
|
||||||
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...HEAD
|
||||||
|
[v0.105.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...v0.105.1
|
||||||
|
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
||||||
[v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3
|
[v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3
|
||||||
[v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2
|
[v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ 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).
|
"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).
|
||||||
|
|
||||||
However, 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 tradtional ad blockers).
|
However, 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).
|
||||||
|
|
||||||
**Known limitations**
|
**Known limitations**
|
||||||
|
|
||||||
@@ -192,6 +192,12 @@ cd AdGuardHome
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Please note, that the non-standard `-j` flag is currently not supported, so
|
||||||
|
building with `make -j 4` or setting your `MAKEFLAGS` to include, for example,
|
||||||
|
`-j 4` is likely to break the build. If you do have your `MAKEFLAGS` set to
|
||||||
|
that, and you don't want to change it, you can override it by running
|
||||||
|
`make -j 1`.
|
||||||
|
|
||||||
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
||||||
|
|
||||||
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project.
|
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project.
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Няслушны фармат IP-адраса",
|
"form_error_ip_format": "Няслушны фармат IP-адраса",
|
||||||
"form_error_mac_format": "Некарэктны фармат MAC",
|
"form_error_mac_format": "Некарэктны фармат MAC",
|
||||||
"form_error_client_id_format": "Няслушны фармат ID кліента",
|
"form_error_client_id_format": "Няслушны фармат ID кліента",
|
||||||
|
"form_error_server_name": "Няслушнае імя сервера",
|
||||||
"form_error_positive": "Павінна быць больш 0",
|
"form_error_positive": "Павінна быць больш 0",
|
||||||
"form_error_negative": "Павінна быць не менш 0",
|
"form_error_negative": "Павінна быць не менш 0",
|
||||||
"range_end_error": "Павінен перавышаць пачатак дыяпазону",
|
"range_end_error": "Павінен перавышаць пачатак дыяпазону",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Свой IP",
|
"custom_ip": "Свой IP",
|
||||||
"blocking_ipv4": "Блакаванне IPv4",
|
"blocking_ipv4": "Блакаванне IPv4",
|
||||||
"blocking_ipv6": "Блакаванне IPv6",
|
"blocking_ipv6": "Блакаванне IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "Ідэнтыфікатар кліента",
|
||||||
|
"client_id_placeholder": "Увядзіце ідэнтыфікатар кліента",
|
||||||
|
"client_id_desc": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. <a>Тут</a> вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.",
|
||||||
"download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS",
|
"download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Загрузіць файл канфігурацыі",
|
||||||
"plain_dns": "Нешыфраваны DNS",
|
"plain_dns": "Нешыфраваны DNS",
|
||||||
"form_enter_rate_limit": "Увядзіце rate limit",
|
"form_enter_rate_limit": "Увядзіце rate limit",
|
||||||
"rate_limit": "Ограничение скорости",
|
"rate_limit": "Ограничение скорости",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Крыніца",
|
"source_label": "Крыніца",
|
||||||
"found_in_known_domain_db": "Знойдзены ў базе вядомых даменаў.",
|
"found_in_known_domain_db": "Знойдзены ў базе вядомых даменаў.",
|
||||||
"category_label": "Катэгорыя",
|
"category_label": "Катэгорыя",
|
||||||
"rule_label": "Правіла",
|
"rule_label": "Правіла(ы)",
|
||||||
"list_label": "Спіс",
|
"list_label": "Спіс",
|
||||||
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
||||||
"known_tracker": "Вядомы трэкер",
|
"known_tracker": "Вядомы трэкер",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут</0> і <1>тут</1>.",
|
"setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут</0> і <1>тут</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS",
|
||||||
"setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
|
"setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
|
||||||
"rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена",
|
"rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена",
|
||||||
"rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена",
|
"rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP-адрасы: {{ip}}",
|
"check_ip": "IP-адрасы: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Прычына: {{reason}}",
|
"check_reason": "Прычына: {{reason}}",
|
||||||
"check_rule": "Правіла: {{rule}}",
|
|
||||||
"check_service": "Назва сэрвісу: {{service}}",
|
"check_service": "Назва сэрвісу: {{service}}",
|
||||||
"service_name": "Назва сэрвіса",
|
"service_name": "Назва сэрвіса",
|
||||||
"check_not_found": "Не знойдзена ў вашым спісе фільтраў",
|
"check_not_found": "Не знойдзена ў вашым спісе фільтраў",
|
||||||
|
|||||||
@@ -144,7 +144,6 @@
|
|||||||
"source_label": "Източник",
|
"source_label": "Източник",
|
||||||
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
||||||
"category_label": "Категория",
|
"category_label": "Категория",
|
||||||
"rule_label": "Правило",
|
|
||||||
"unknown_filter": "Непознат филтър {{filterId}}",
|
"unknown_filter": "Непознат филтър {{filterId}}",
|
||||||
"install_welcome_title": "Добре дошли в AdGuard Home!",
|
"install_welcome_title": "Добре дошли в AdGuard Home!",
|
||||||
"install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.",
|
"install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.",
|
||||||
@@ -202,7 +201,6 @@
|
|||||||
"encryption_config_saved": "Конфигурацията е успешно записана",
|
"encryption_config_saved": "Конфигурацията е успешно записана",
|
||||||
"encryption_server": "Име на сървъра",
|
"encryption_server": "Име на сървъра",
|
||||||
"encryption_server_enter": "Въведете име на домейна",
|
"encryption_server_enter": "Въведете име на домейна",
|
||||||
"encryption_server_desc": "За да използвате HTTPS, трябва името на сървъра да съвпада с това на SSL сертификата.",
|
|
||||||
"encryption_redirect": "Автоматично пренасочване към HTTPS",
|
"encryption_redirect": "Автоматично пренасочване към HTTPS",
|
||||||
"encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.",
|
"encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.",
|
||||||
"encryption_https": "HTTPS порт",
|
"encryption_https": "HTTPS порт",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Neplatný formát IP",
|
"form_error_ip_format": "Neplatný formát IP",
|
||||||
"form_error_mac_format": "Neplatný formát MAC",
|
"form_error_mac_format": "Neplatný formát MAC",
|
||||||
"form_error_client_id_format": "Neplatný formát ID klienta",
|
"form_error_client_id_format": "Neplatný formát ID klienta",
|
||||||
|
"form_error_server_name": "Neplatný název serveru",
|
||||||
"form_error_positive": "Musí být větší než 0",
|
"form_error_positive": "Musí být větší než 0",
|
||||||
"form_error_negative": "Musí být rovno nebo větší než 0",
|
"form_error_negative": "Musí být rovno nebo větší než 0",
|
||||||
"range_end_error": "Musí být větší než začátek rozsahu",
|
"range_end_error": "Musí být větší než začátek rozsahu",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Vlastní IP",
|
"custom_ip": "Vlastní IP",
|
||||||
"blocking_ipv4": "Blokování IPv4",
|
"blocking_ipv4": "Blokování IPv4",
|
||||||
"blocking_ipv6": "Blokování IPv6",
|
"blocking_ipv6": "Blokování IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS přes HTTPS",
|
"dns_over_https": "DNS přes HTTPS",
|
||||||
"dns_over_tls": "DNS přes TLS",
|
"dns_over_tls": "DNS přes TLS",
|
||||||
|
"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. <a>Zde</a> 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_doh": "Stáhnout .mobileconfig pro DNS skrze HTTPS",
|
||||||
"download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS",
|
"download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS",
|
||||||
|
"download_mobileconfig": "Stáhnout konfigurační soubor",
|
||||||
"plain_dns": "Čisté DNS",
|
"plain_dns": "Čisté DNS",
|
||||||
"form_enter_rate_limit": "Zadejte rychlostní limit",
|
"form_enter_rate_limit": "Zadejte rychlostní limit",
|
||||||
"rate_limit": "Rychlostní limit",
|
"rate_limit": "Rychlostní limit",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Zdroj",
|
"source_label": "Zdroj",
|
||||||
"found_in_known_domain_db": "Nalezeno v databázi známých domén",
|
"found_in_known_domain_db": "Nalezeno v databázi známých domén",
|
||||||
"category_label": "Kategorie",
|
"category_label": "Kategorie",
|
||||||
"rule_label": "Pravidlo",
|
"rule_label": "Pravidla",
|
||||||
"list_label": "Seznam",
|
"list_label": "Seznam",
|
||||||
"unknown_filter": "Neznámý filtr {{filterId}}",
|
"unknown_filter": "Neznámý filtr {{filterId}}",
|
||||||
"known_tracker": "Známý slídič",
|
"known_tracker": "Známý slídič",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Konfigurace šifrování byla uložena",
|
"encryption_config_saved": "Konfigurace šifrování byla uložena",
|
||||||
"encryption_server": "Název serveru",
|
"encryption_server": "Název serveru",
|
||||||
"encryption_server_enter": "Zadejte název domény",
|
"encryption_server_enter": "Zadejte název domény",
|
||||||
"encryption_server_desc": "Abyste mohli používat protokol HTTPS, musíte zadat název serveru, který odpovídá vašemu certifikátu SSL.",
|
"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_redirect": "Automaticky přesměrovat na HTTPS",
|
"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_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": "HTTPS port",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Upravit klienta",
|
"client_edit": "Upravit klienta",
|
||||||
"client_identifier": "Identifikátor",
|
"client_identifier": "Identifikátor",
|
||||||
"ip_address": "IP adresa",
|
"ip_address": "IP adresa",
|
||||||
"client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy, CIDR nebo MAC adresy. Upozorňujeme, že použití MAC jako identifikátoru je možné pouze v případě, že je AdGuard Home také <0>DHCP server</0>",
|
"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</0> se můžete dozvědět více o tom, jak klienty identifikovat.",
|
||||||
"form_enter_ip": "Zadejte IP",
|
"form_enter_ip": "Zadejte IP",
|
||||||
"form_enter_mac": "Zadejte MAC",
|
"form_enter_mac": "Zadejte MAC",
|
||||||
"form_enter_id": "Zadejte identifikátor",
|
"form_enter_id": "Zadejte identifikátor",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podporuje <1>DNS-přes-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podporuje <1>DNS-přes-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podporuje <1>DNS-přes-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podporuje <1>DNS-přes-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Další implementace naleznete <0>zde</0> a <1>zde</1>.",
|
"setup_dns_privacy_other_5": "Další implementace naleznete <0>zde</0> a <1>zde</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Konfigurace pro iOS a macOS",
|
||||||
"setup_dns_notice": "Pro použití <1>DNS-přes-HTTPS</1> nebo <1>DNS-přes-TLS</1> potřebujete v nastaveních AdGuard Home <0>nakonfigurovat šifrování</0>.",
|
"setup_dns_notice": "Pro použití <1>DNS-přes-HTTPS</1> nebo <1>DNS-přes-TLS</1> potřebujete v nastaveních AdGuard Home <0>nakonfigurovat šifrování</0>.",
|
||||||
"rewrite_added": "Přesměrování DNS pro „{{key}}“ úspěšně přidáno",
|
"rewrite_added": "Přesměrování DNS pro „{{key}}“ úspěšně přidáno",
|
||||||
"rewrite_deleted": "Přesměrování DNS pro „{{key}}“ úspěšně smazáno",
|
"rewrite_deleted": "Přesměrování DNS pro „{{key}}“ úspěšně smazáno",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP adresy: {{ip}}",
|
"check_ip": "IP adresy: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Důvod: {{reason}}",
|
"check_reason": "Důvod: {{reason}}",
|
||||||
"check_rule": "Pravidlo: {{rule}}",
|
|
||||||
"check_service": "Název služby: {{service}}",
|
"check_service": "Název služby: {{service}}",
|
||||||
"service_name": "Název služby",
|
"service_name": "Název služby",
|
||||||
"check_not_found": "Nenalezeno ve Vašich seznamech filtrů",
|
"check_not_found": "Nenalezeno ve Vašich seznamech filtrů",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Ugyldigt IP-format",
|
"form_error_ip_format": "Ugyldigt IP-format",
|
||||||
"form_error_mac_format": "Ugyldigt MAC-format",
|
"form_error_mac_format": "Ugyldigt MAC-format",
|
||||||
"form_error_client_id_format": "Ugyldigt klient-ID-format",
|
"form_error_client_id_format": "Ugyldigt klient-ID-format",
|
||||||
|
"form_error_server_name": "Ugyldigt servernavn",
|
||||||
"form_error_positive": "Skal være større end 0",
|
"form_error_positive": "Skal være større end 0",
|
||||||
"form_error_negative": "Skal være lig med 0 eller større",
|
"form_error_negative": "Skal være lig med 0 eller større",
|
||||||
"range_end_error": "Skal være større end starten af intervallet",
|
"range_end_error": "Skal være større end starten af intervallet",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Tilpasset IP",
|
"custom_ip": "Tilpasset IP",
|
||||||
"blocking_ipv4": "IPv4-blokering",
|
"blocking_ipv4": "IPv4-blokering",
|
||||||
"blocking_ipv6": "IPv6-blokering",
|
"blocking_ipv6": "IPv6-blokering",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-Quic",
|
||||||
|
"client_id": "Klient-ID",
|
||||||
|
"client_id_placeholder": "Indtast klient-ID",
|
||||||
|
"client_id_desc": "Forskellige klienter kan identificeres ved hjælp af et specielt klient-ID. <a>Her</a> kan du lære mere om, hvordan du identificerer klienter.",
|
||||||
"download_mobileconfig_doh": "Download .mobileconfig til DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Download .mobileconfig til DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Download .mobileconfig til DNS-over-TLS",
|
"download_mobileconfig_dot": "Download .mobileconfig til DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Download konfigurationsfil",
|
||||||
"plain_dns": "Almindelig DNS",
|
"plain_dns": "Almindelig DNS",
|
||||||
"form_enter_rate_limit": "Indtast hyppighedsgrænse",
|
"form_enter_rate_limit": "Indtast hyppighedsgrænse",
|
||||||
"rate_limit": "Hyppighedsgrænse",
|
"rate_limit": "Hyppighedsgrænse",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Kilde",
|
"source_label": "Kilde",
|
||||||
"found_in_known_domain_db": "Fundet i databasen med kendte domæner.",
|
"found_in_known_domain_db": "Fundet i databasen med kendte domæner.",
|
||||||
"category_label": "Kategori",
|
"category_label": "Kategori",
|
||||||
"rule_label": "Regel",
|
"rule_label": "Regel(regler)",
|
||||||
"list_label": "Liste",
|
"list_label": "Liste",
|
||||||
"unknown_filter": "Ukendt filter {{filterId}}",
|
"unknown_filter": "Ukendt filter {{filterId}}",
|
||||||
"known_tracker": "Kendt tracker",
|
"known_tracker": "Kendt tracker",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Krypteringskonfiguration gemt",
|
"encryption_config_saved": "Krypteringskonfiguration gemt",
|
||||||
"encryption_server": "Servernavn",
|
"encryption_server": "Servernavn",
|
||||||
"encryption_server_enter": "Indtast dit domænenavn",
|
"encryption_server_enter": "Indtast dit domænenavn",
|
||||||
"encryption_server_desc": "For at kunne bruge HTTPS skal du indtaste servernavnet, der matcher dit SSL-certifikat.",
|
"encryption_server_desc": "For at kunne bruge HTTPS skal du indtaste det servernavn, der matcher dit SSL-certifikat eller wildcard-certifikat. Hvis feltet ikke er indstillet, accepterer det TLS-forbindelser til ethvert domæne.",
|
||||||
"encryption_redirect": "Omdiriger automatisk til HTTPS",
|
"encryption_redirect": "Omdiriger automatisk til HTTPS",
|
||||||
"encryption_redirect_desc": "Hvis afkrydset, vil AdGuard Home automatisk omdirigere dig fra HTTP til HTTPS-adresser.",
|
"encryption_redirect_desc": "Hvis afkrydset, vil AdGuard Home automatisk omdirigere dig fra HTTP til HTTPS-adresser.",
|
||||||
"encryption_https": "HTTPS-port",
|
"encryption_https": "HTTPS-port",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Rediger Klient",
|
"client_edit": "Rediger Klient",
|
||||||
"client_identifier": "Identifikator",
|
"client_identifier": "Identifikator",
|
||||||
"ip_address": "IP-adresse",
|
"ip_address": "IP-adresse",
|
||||||
"client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen, CIDR eller MAC-adressen. Bemærk, at det kun er muligt at bruge MAC som identifikator, hvis AdGuard Home også er en <0>DHCP-server</0>",
|
"client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen, CIDR eller MAC-adressen eller et specielt klient-ID (kan bruges til DoT/DoH/DoQ). <0>Her</0> kan du lære mere om, hvordan du identificerer klienter.",
|
||||||
"form_enter_ip": "Indtast IP",
|
"form_enter_ip": "Indtast IP",
|
||||||
"form_enter_mac": "Indtast MAC",
|
"form_enter_mac": "Indtast MAC",
|
||||||
"form_enter_id": "Indtast identifikator",
|
"form_enter_id": "Indtast identifikator",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> understøtter <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> understøtter <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> understøtter <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> understøtter <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Du kan finde flere implementeringer <0>her</0> og <1>her</1>.",
|
"setup_dns_privacy_other_5": "Du kan finde flere implementeringer <0>her</0> og <1>her</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS- og macOS-konfiguration",
|
||||||
"setup_dns_notice": "For at kunne bruge <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, skal du <0>konfigurere Krypteringen</0> i indstillingerne i AdGuard Home.",
|
"setup_dns_notice": "For at kunne bruge <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, skal du <0>konfigurere Krypteringen</0> i indstillingerne i AdGuard Home.",
|
||||||
"rewrite_added": "DNS-omskrivning for \"{{key}}\" blev tilføjet",
|
"rewrite_added": "DNS-omskrivning for \"{{key}}\" blev tilføjet",
|
||||||
"rewrite_deleted": "DNS-omskrivning for \"{{key}}\" blev slettet",
|
"rewrite_deleted": "DNS-omskrivning for \"{{key}}\" blev slettet",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP-adresser: {{ip}}",
|
"check_ip": "IP-adresser: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Årsag: {{reason}}",
|
"check_reason": "Årsag: {{reason}}",
|
||||||
"check_rule": "Regel: {{rule}}",
|
|
||||||
"check_service": "Servicenavn: {{service}}",
|
"check_service": "Servicenavn: {{service}}",
|
||||||
"service_name": "Navn på tjeneste",
|
"service_name": "Navn på tjeneste",
|
||||||
"check_not_found": "Ikke fundet i dine filterlister",
|
"check_not_found": "Ikke fundet i dine filterlister",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Ungültiges IPv4-Format",
|
"form_error_ip_format": "Ungültiges IPv4-Format",
|
||||||
"form_error_mac_format": "Ungültiges MAC-Format",
|
"form_error_mac_format": "Ungültiges MAC-Format",
|
||||||
"form_error_client_id_format": "Ungültiges Client-ID-Format",
|
"form_error_client_id_format": "Ungültiges Client-ID-Format",
|
||||||
|
"form_error_server_name": "Ungültiger Servername",
|
||||||
"form_error_positive": "Muss größer als 0 sein.",
|
"form_error_positive": "Muss größer als 0 sein.",
|
||||||
"form_error_negative": "Muss gleich oder größer als 0 (Null) sein",
|
"form_error_negative": "Muss gleich oder größer als 0 (Null) sein",
|
||||||
"range_end_error": "Muss größer als der Bereichsbeginn sein",
|
"range_end_error": "Muss größer als der Bereichsbeginn sein",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Benutzerdefinierte IP",
|
"custom_ip": "Benutzerdefinierte IP",
|
||||||
"blocking_ipv4": "IPv4-Sperren",
|
"blocking_ipv4": "IPv4-Sperren",
|
||||||
"blocking_ipv6": "IPv6-Sperren",
|
"blocking_ipv6": "IPv6-Sperren",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS (DNS-Abrage über HTTPS)",
|
"dns_over_https": "DNS-over-HTTPS (DNS-Abrage über HTTPS)",
|
||||||
"dns_over_tls": "DNS-over-TLS (DNS-Abrage über TLS)",
|
"dns_over_tls": "DNS-over-TLS (DNS-Abrage über TLS)",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "Client-ID",
|
||||||
|
"client_id_placeholder": "Client-ID eingeben",
|
||||||
|
"client_id_desc": "Verschiedene Clients können durch eine spezielle Client-ID identifiziert werden. <a>Hier</a> können Sie mehr darüber erfahren, wie Sie Clients identifizieren können.",
|
||||||
"download_mobileconfig_doh": ".mobileconfig für DNS-über-HTTPS herunterladen",
|
"download_mobileconfig_doh": ".mobileconfig für DNS-über-HTTPS herunterladen",
|
||||||
"download_mobileconfig_dot": ".mobileconfig für DNS-über-TLS herunterladen",
|
"download_mobileconfig_dot": ".mobileconfig für DNS-über-TLS herunterladen",
|
||||||
|
"download_mobileconfig": "Konfigurationsdatei herunterladen",
|
||||||
"plain_dns": "Einfaches DNS",
|
"plain_dns": "Einfaches DNS",
|
||||||
"form_enter_rate_limit": "Begrenzungswert eingeben",
|
"form_enter_rate_limit": "Begrenzungswert eingeben",
|
||||||
"rate_limit": "Begrenzungswert",
|
"rate_limit": "Begrenzungswert",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Quelle",
|
"source_label": "Quelle",
|
||||||
"found_in_known_domain_db": "In der Datenbank der bekannten Domains gefunden.",
|
"found_in_known_domain_db": "In der Datenbank der bekannten Domains gefunden.",
|
||||||
"category_label": "Kategorie",
|
"category_label": "Kategorie",
|
||||||
"rule_label": "Regel",
|
"rule_label": "Regel(n)",
|
||||||
"list_label": "Liste",
|
"list_label": "Liste",
|
||||||
"unknown_filter": "Unbekannter Filter {{filterId}}",
|
"unknown_filter": "Unbekannter Filter {{filterId}}",
|
||||||
"known_tracker": "Bekannte Tracker",
|
"known_tracker": "Bekannte Tracker",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> unterstützt <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> unterstützt <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> unterstützt <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> unterstützt <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Weitere Umsetzungen finden Sie <0>hier</0> und <1>hier</1>.",
|
"setup_dns_privacy_other_5": "Weitere Umsetzungen finden Sie <0>hier</0> und <1>hier</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Konfiguration für iOS und macOS",
|
||||||
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
|
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
|
||||||
"rewrite_added": "DNS-Umschreibung für „{{key}}” erfolgreich hinzugefügt",
|
"rewrite_added": "DNS-Umschreibung für „{{key}}” erfolgreich hinzugefügt",
|
||||||
"rewrite_deleted": "DNS-Umschreibung für „{{key}}” erfolgreich entfernt",
|
"rewrite_deleted": "DNS-Umschreibung für „{{key}}” erfolgreich entfernt",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP-Adressen: {{ip}}",
|
"check_ip": "IP-Adressen: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Grund: {{reason}}",
|
"check_reason": "Grund: {{reason}}",
|
||||||
"check_rule": "Regel: {{rule}}",
|
|
||||||
"check_service": "Dienstname: {{service}}",
|
"check_service": "Dienstname: {{service}}",
|
||||||
"service_name": "Name des Dienstes",
|
"service_name": "Name des Dienstes",
|
||||||
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Invalid IP format",
|
"form_error_ip_format": "Invalid IP format",
|
||||||
"form_error_mac_format": "Invalid MAC format",
|
"form_error_mac_format": "Invalid MAC format",
|
||||||
"form_error_client_id_format": "Invalid client ID format",
|
"form_error_client_id_format": "Invalid client ID format",
|
||||||
|
"form_error_server_name": "Invalid server name",
|
||||||
"form_error_positive": "Must be greater than 0",
|
"form_error_positive": "Must be greater than 0",
|
||||||
"form_error_negative": "Must be equal to 0 or greater",
|
"form_error_negative": "Must be equal to 0 or greater",
|
||||||
"range_end_error": "Must be greater than range start",
|
"range_end_error": "Must be greater than range start",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Custom IP",
|
"custom_ip": "Custom IP",
|
||||||
"blocking_ipv4": "Blocking IPv4",
|
"blocking_ipv4": "Blocking IPv4",
|
||||||
"blocking_ipv6": "Blocking IPv6",
|
"blocking_ipv6": "Blocking IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "Client ID",
|
||||||
|
"client_id_placeholder": "Enter client ID",
|
||||||
|
"client_id_desc": "Different clients can be identified by a special client ID. <a>Here</a> you can learn more about how to identify clients.",
|
||||||
"download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS",
|
"download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Download configuration file",
|
||||||
"plain_dns": "Plain DNS",
|
"plain_dns": "Plain DNS",
|
||||||
"form_enter_rate_limit": "Enter rate limit",
|
"form_enter_rate_limit": "Enter rate limit",
|
||||||
"rate_limit": "Rate limit",
|
"rate_limit": "Rate limit",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Source",
|
"source_label": "Source",
|
||||||
"found_in_known_domain_db": "Found in the known domains database.",
|
"found_in_known_domain_db": "Found in the known domains database.",
|
||||||
"category_label": "Category",
|
"category_label": "Category",
|
||||||
"rule_label": "Rule",
|
"rule_label": "Rule(s)",
|
||||||
"list_label": "List",
|
"list_label": "List",
|
||||||
"unknown_filter": "Unknown filter {{filterId}}",
|
"unknown_filter": "Unknown filter {{filterId}}",
|
||||||
"known_tracker": "Known tracker",
|
"known_tracker": "Known tracker",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Encryption config saved",
|
"encryption_config_saved": "Encryption config saved",
|
||||||
"encryption_server": "Server name",
|
"encryption_server": "Server name",
|
||||||
"encryption_server_enter": "Enter your domain 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.",
|
"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_redirect": "Redirect to HTTPS automatically",
|
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||||
"encryption_https": "HTTPS port",
|
"encryption_https": "HTTPS port",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Edit Client",
|
"client_edit": "Edit Client",
|
||||||
"client_identifier": "Identifier",
|
"client_identifier": "Identifier",
|
||||||
"ip_address": "IP address",
|
"ip_address": "IP address",
|
||||||
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server</0>",
|
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or a special client ID (can be used for DoT/DoH/DoQ). <0>Here</0> you can learn more about how to identify clients.",
|
||||||
"form_enter_ip": "Enter IP",
|
"form_enter_ip": "Enter IP",
|
||||||
"form_enter_mac": "Enter MAC",
|
"form_enter_mac": "Enter MAC",
|
||||||
"form_enter_id": "Enter identifier",
|
"form_enter_id": "Enter identifier",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supports <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supports <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supports <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supports <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "You will find more implementations <0>here</0> and <1>here</1>.",
|
"setup_dns_privacy_other_5": "You will find more implementations <0>here</0> and <1>here</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS and macOS configuration",
|
||||||
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.",
|
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.",
|
||||||
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
|
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
|
||||||
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
|
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP addresses: {{ip}}",
|
"check_ip": "IP addresses: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Reason: {{reason}}",
|
"check_reason": "Reason: {{reason}}",
|
||||||
"check_rule": "Rule: {{rule}}",
|
|
||||||
"check_service": "Service name: {{service}}",
|
"check_service": "Service name: {{service}}",
|
||||||
"service_name": "Service name",
|
"service_name": "Service name",
|
||||||
"check_not_found": "Not found in your filter lists",
|
"check_not_found": "Not found in your filter lists",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Formato IP no válido",
|
"form_error_ip_format": "Formato IP no válido",
|
||||||
"form_error_mac_format": "Formato MAC no válido",
|
"form_error_mac_format": "Formato MAC no válido",
|
||||||
"form_error_client_id_format": "Formato de ID de cliente no válido",
|
"form_error_client_id_format": "Formato de ID de cliente no válido",
|
||||||
|
"form_error_server_name": "Nombre de servidor no válido",
|
||||||
"form_error_positive": "Debe ser mayor que 0",
|
"form_error_positive": "Debe ser mayor que 0",
|
||||||
"form_error_negative": "Debe ser igual o mayor que 0",
|
"form_error_negative": "Debe ser igual o mayor que 0",
|
||||||
"range_end_error": "Debe ser mayor que el inicio de rango",
|
"range_end_error": "Debe ser mayor que el inicio de rango",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP personalizada",
|
"custom_ip": "IP personalizada",
|
||||||
"blocking_ipv4": "Bloqueo de IPv4",
|
"blocking_ipv4": "Bloqueo de IPv4",
|
||||||
"blocking_ipv6": "Bloqueo de IPv6",
|
"blocking_ipv6": "Bloqueo de IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS mediante HTTPS",
|
"dns_over_https": "DNS mediante HTTPS",
|
||||||
"dns_over_tls": "DNS mediante TLS",
|
"dns_over_tls": "DNS mediante TLS",
|
||||||
|
"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. <a>Aquí</a> puedes obtener más información sobre cómo identificar clientes.",
|
||||||
"download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS",
|
"download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS",
|
||||||
"download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS",
|
"download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS",
|
||||||
|
"download_mobileconfig": "Descargar archivo de configuración",
|
||||||
"plain_dns": "DNS simple",
|
"plain_dns": "DNS simple",
|
||||||
"form_enter_rate_limit": "Ingresa el límite de cantidad",
|
"form_enter_rate_limit": "Ingresa el límite de cantidad",
|
||||||
"rate_limit": "Límite de cantidad",
|
"rate_limit": "Límite de cantidad",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Configuración de cifrado guardado",
|
"encryption_config_saved": "Configuración de cifrado guardado",
|
||||||
"encryption_server": "Nombre del servidor",
|
"encryption_server": "Nombre del servidor",
|
||||||
"encryption_server_enter": "Ingresa el nombre del dominio",
|
"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.",
|
"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_redirect": "Redireccionar a HTTPS automáticamente",
|
"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_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.",
|
||||||
"encryption_https": "Puerto HTTPS",
|
"encryption_https": "Puerto HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Editar cliente",
|
"client_edit": "Editar cliente",
|
||||||
"client_identifier": "Identificador",
|
"client_identifier": "Identificador",
|
||||||
"ip_address": "Dirección IP",
|
"ip_address": "Dirección IP",
|
||||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC y CIDR. Ten en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home también es un <0>servidor DHCP</0>",
|
"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í</0> puedes obtener más información sobre cómo identificar clientes.",
|
||||||
"form_enter_ip": "Ingresa la IP",
|
"form_enter_ip": "Ingresa la IP",
|
||||||
"form_enter_mac": "Ingresa la MAC",
|
"form_enter_mac": "Ingresa la MAC",
|
||||||
"form_enter_id": "Ingresa el identificador",
|
"form_enter_id": "Ingresa el identificador",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> soporta <1>DNS mediante HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> soporta <1>DNS mediante HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí</0> y <1>aquí</1>.",
|
"setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí</0> y <1>aquí</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Configuración de iOS y macOS",
|
||||||
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debes <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
|
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debes <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
|
||||||
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
|
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
|
||||||
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
|
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Direcciones IP: {{ip}}",
|
"check_ip": "Direcciones IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Razón: {{reason}}",
|
"check_reason": "Razón: {{reason}}",
|
||||||
"check_rule": "Regla: {{rule}}",
|
|
||||||
"check_service": "Nombre del servicio: {{service}}",
|
"check_service": "Nombre del servicio: {{service}}",
|
||||||
"service_name": "Nombre del servicio",
|
"service_name": "Nombre del servicio",
|
||||||
"check_not_found": "No se ha encontrado en tus listas de filtros",
|
"check_not_found": "No se ha encontrado en tus listas de filtros",
|
||||||
|
|||||||
@@ -239,7 +239,6 @@
|
|||||||
"source_label": "منبع",
|
"source_label": "منبع",
|
||||||
"found_in_known_domain_db": "در پایگاه داده دامنه های شناخته شده پیدا شد",
|
"found_in_known_domain_db": "در پایگاه داده دامنه های شناخته شده پیدا شد",
|
||||||
"category_label": "دسته بندی",
|
"category_label": "دسته بندی",
|
||||||
"rule_label": "دستور",
|
|
||||||
"list_label": "لیست",
|
"list_label": "لیست",
|
||||||
"unknown_filter": "فیلتر ناشناخته {{filterId}}",
|
"unknown_filter": "فیلتر ناشناخته {{filterId}}",
|
||||||
"known_tracker": "ردیاب های شناخته شده",
|
"known_tracker": "ردیاب های شناخته شده",
|
||||||
@@ -300,7 +299,6 @@
|
|||||||
"encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد",
|
"encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد",
|
||||||
"encryption_server": "نام سرور",
|
"encryption_server": "نام سرور",
|
||||||
"encryption_server_enter": "نام دامنه خود را وارد کنید",
|
"encryption_server_enter": "نام دامنه خود را وارد کنید",
|
||||||
"encryption_server_desc": "به منظور استفاده از HTTPS،شما باید نام سرور مطابق با گواهینامه اِس اِس اِل را وارد کنید.",
|
|
||||||
"encryption_redirect": "تغییر مسیر خودکار به HTTPS",
|
"encryption_redirect": "تغییر مسیر خودکار به HTTPS",
|
||||||
"encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home خودکار شما را از آدرس HTTP به HTTPS منتقل می کند",
|
"encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home خودکار شما را از آدرس HTTP به HTTPS منتقل می کند",
|
||||||
"encryption_https": "پورت HTTPS",
|
"encryption_https": "پورت HTTPS",
|
||||||
@@ -354,7 +352,6 @@
|
|||||||
"client_edit": "ویرایش کلاینت",
|
"client_edit": "ویرایش کلاینت",
|
||||||
"client_identifier": "احراز با",
|
"client_identifier": "احراز با",
|
||||||
"ip_address": "آدرس آی پی",
|
"ip_address": "آدرس آی پی",
|
||||||
"client_identifier_desc": "کلاینت میتواند با آدرس آی پی یا آدرس مَک احراز شود. لطفا توجه کنید،که استفاده از مَک بعنوان عامل احراز زمانی امکان دارد که AdGuard Home نیز <0>سرور DHCP </0> باشد",
|
|
||||||
"form_enter_ip": "آی پی را وارد کنید",
|
"form_enter_ip": "آی پی را وارد کنید",
|
||||||
"form_enter_mac": "مَک را وارد کنید",
|
"form_enter_mac": "مَک را وارد کنید",
|
||||||
"form_enter_id": "خطای احرازکننده",
|
"form_enter_id": "خطای احرازکننده",
|
||||||
@@ -487,7 +484,6 @@
|
|||||||
"check_ip": "آدرس آی پی: {{ip}}",
|
"check_ip": "آدرس آی پی: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "علت: {{reason}}",
|
"check_reason": "علت: {{reason}}",
|
||||||
"check_rule": "دستور: {{rule}}",
|
|
||||||
"check_service": "نام سرویس: {{service}}",
|
"check_service": "نام سرویس: {{service}}",
|
||||||
"check_not_found": "در لیست فیلترهای شما یافت نشد",
|
"check_not_found": "در لیست فیلترهای شما یافت نشد",
|
||||||
"client_confirm_block": "آیا واقعا میخواهید کلاینت \"{{ip}}\" را مسدود کنید؟",
|
"client_confirm_block": "آیا واقعا میخواهید کلاینت \"{{ip}}\" را مسدود کنید؟",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Format IPv4 invalide",
|
"form_error_ip_format": "Format IPv4 invalide",
|
||||||
"form_error_mac_format": "Format MAC invalide",
|
"form_error_mac_format": "Format MAC invalide",
|
||||||
"form_error_client_id_format": "Format d'ID client non valide",
|
"form_error_client_id_format": "Format d'ID client non valide",
|
||||||
|
"form_error_server_name": "Nom de serveur invalide",
|
||||||
"form_error_positive": "Doit être supérieur à 0",
|
"form_error_positive": "Doit être supérieur à 0",
|
||||||
"form_error_negative": "Doit être égal à 0 ou supérieur",
|
"form_error_negative": "Doit être égal à 0 ou supérieur",
|
||||||
"range_end_error": "Doit être supérieur au début de la gamme",
|
"range_end_error": "Doit être supérieur au début de la gamme",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP personnalisée",
|
"custom_ip": "IP personnalisée",
|
||||||
"blocking_ipv4": "Blocage IPv4",
|
"blocking_ipv4": "Blocage IPv4",
|
||||||
"blocking_ipv6": "Blocage IPv6",
|
"blocking_ipv6": "Blocage IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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 <a>ici</a> .",
|
||||||
"download_mobileconfig_doh": "Télécharger .mobileconfig pour DNS-sur-HTTPS",
|
"download_mobileconfig_doh": "Télécharger .mobileconfig pour DNS-sur-HTTPS",
|
||||||
"download_mobileconfig_dot": "Télécharger .mobileconfig pour DNS-sur-TLS",
|
"download_mobileconfig_dot": "Télécharger .mobileconfig pour DNS-sur-TLS",
|
||||||
|
"download_mobileconfig": "Télécharger le fichier de configuration",
|
||||||
"plain_dns": "DNS brut",
|
"plain_dns": "DNS brut",
|
||||||
"form_enter_rate_limit": "Entrez la limite de taux",
|
"form_enter_rate_limit": "Entrez la limite de taux",
|
||||||
"rate_limit": "Limite de taux",
|
"rate_limit": "Limite de taux",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Source",
|
"source_label": "Source",
|
||||||
"found_in_known_domain_db": "Trouvé dans la base de données des domaines connus",
|
"found_in_known_domain_db": "Trouvé dans la base de données des domaines connus",
|
||||||
"category_label": "Catégorie",
|
"category_label": "Catégorie",
|
||||||
"rule_label": "Règle",
|
"rule_label": "Règle(s)",
|
||||||
"list_label": "Liste",
|
"list_label": "Liste",
|
||||||
"unknown_filter": "Filtre inconnu {{filterId}}",
|
"unknown_filter": "Filtre inconnu {{filterId}}",
|
||||||
"known_tracker": "Pisteur connu",
|
"known_tracker": "Pisteur connu",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Configuration de chiffrement enregistrée",
|
"encryption_config_saved": "Configuration de chiffrement enregistrée",
|
||||||
"encryption_server": "Nom du serveur",
|
"encryption_server": "Nom du serveur",
|
||||||
"encryption_server_enter": "Entrez votre nom de domaine",
|
"encryption_server_enter": "Entrez votre nom de domaine",
|
||||||
"encryption_server_desc": "Pour utiliser HTTPS, vous devez entrer le nom du serveur qui correspond à votre certificat SSL.",
|
"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_redirect": "Redirection automatiquement vers HTTPS",
|
"encryption_redirect": "Redirection automatiquement vers HTTPS",
|
||||||
"encryption_redirect_desc": "Si coché, AdGuard Home vous redirigera automatiquement d'adresses HTTP vers HTTPS.",
|
"encryption_redirect_desc": "Si coché, AdGuard Home vous redirigera automatiquement d'adresses HTTP vers HTTPS.",
|
||||||
"encryption_https": "Port HTTPS",
|
"encryption_https": "Port HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Modifier le client",
|
"client_edit": "Modifier le client",
|
||||||
"client_identifier": "Identifiant",
|
"client_identifier": "Identifiant",
|
||||||
"ip_address": "Adresse IP",
|
"ip_address": "Adresse IP",
|
||||||
"client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP ou MAC. Veuillez noter que l'utilisation de l'adresse MAC comme identifiant est possible uniquement si AdGuard Home est aussi un <0>serveur DHCP</0>",
|
"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</0> .",
|
||||||
"form_enter_ip": "Saisissez l'IP",
|
"form_enter_ip": "Saisissez l'IP",
|
||||||
"form_enter_mac": "Saisissez MAC",
|
"form_enter_mac": "Saisissez MAC",
|
||||||
"form_enter_id": "Entrer identifiant",
|
"form_enter_id": "Entrer identifiant",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supporte le <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supporte le <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supporte le <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supporte le <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Vous trouverez plus d'implémentations <0>ici</0> et <1>ici</1>.",
|
"setup_dns_privacy_other_5": "Vous trouverez plus d'implémentations <0>ici</0> et <1>ici</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Configuration sur iOS et macOS",
|
||||||
"setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS</1> ou le <1>DNS-over-TLS</1>, vous devez <0>configurer le Chiffrement</0> dans les paramètres de AdGuard Home.",
|
"setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS</1> ou le <1>DNS-over-TLS</1>, vous devez <0>configurer le Chiffrement</0> dans les paramètres de AdGuard Home.",
|
||||||
"rewrite_added": "Réécriture DNS pour \"{{key}}\" ajoutée",
|
"rewrite_added": "Réécriture DNS pour \"{{key}}\" ajoutée",
|
||||||
"rewrite_deleted": "Réécriture DNS pour \"{{key}}\" supprimée",
|
"rewrite_deleted": "Réécriture DNS pour \"{{key}}\" supprimée",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Adresses IP : {{ip}}",
|
"check_ip": "Adresses IP : {{ip}}",
|
||||||
"check_cname": "CNAME : {{cname}}",
|
"check_cname": "CNAME : {{cname}}",
|
||||||
"check_reason": "Raison : {{reason}}",
|
"check_reason": "Raison : {{reason}}",
|
||||||
"check_rule": "Règle : {{rule}}",
|
|
||||||
"check_service": "Nom du service : {{service}}",
|
"check_service": "Nom du service : {{service}}",
|
||||||
"service_name": "Nom du service",
|
"service_name": "Nom du service",
|
||||||
"check_not_found": "Introuvable dans vos listes de filtres",
|
"check_not_found": "Introuvable dans vos listes de filtres",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Nevažeći format IP adrese",
|
"form_error_ip_format": "Nevažeći format IP adrese",
|
||||||
"form_error_mac_format": "Nevažeći MAC format",
|
"form_error_mac_format": "Nevažeći MAC format",
|
||||||
"form_error_client_id_format": "Nevažeći format ID-a klijenta",
|
"form_error_client_id_format": "Nevažeći format ID-a klijenta",
|
||||||
|
"form_error_server_name": "Nevažeće ime poslužitelja",
|
||||||
"form_error_positive": "Mora biti veće od 0",
|
"form_error_positive": "Mora biti veće od 0",
|
||||||
"form_error_negative": "Mora biti jednako ili veće od 0",
|
"form_error_negative": "Mora biti jednako ili veće od 0",
|
||||||
"range_end_error": "Mora biti veće od početne vrijednosti raspona",
|
"range_end_error": "Mora biti veće od početne vrijednosti raspona",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Prilagođen IP",
|
"custom_ip": "Prilagođen IP",
|
||||||
"blocking_ipv4": "Blokiranje IPv4",
|
"blocking_ipv4": "Blokiranje IPv4",
|
||||||
"blocking_ipv6": "Blokiranje IPv6",
|
"blocking_ipv6": "Blokiranje IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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": "Razni klijenti mogu biti prepoznati po specijalnom identifikatoru. <a>Ovdje</a> možete saznati više kako možete identificirati klijente.",
|
||||||
"download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
|
"download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Preuzmite konfiguracijsku datoteku",
|
||||||
"plain_dns": "Obični DNS",
|
"plain_dns": "Obični DNS",
|
||||||
"form_enter_rate_limit": "Unesite ograničenje",
|
"form_enter_rate_limit": "Unesite ograničenje",
|
||||||
"rate_limit": "Ograničenje",
|
"rate_limit": "Ograničenje",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje</0> i <1>ovdje</1>.",
|
"setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje</0> i <1>ovdje</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "konfiguracija za iOS i macOS",
|
||||||
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
|
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
|
||||||
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
|
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
|
||||||
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
|
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP adrese: {{ip}}",
|
"check_ip": "IP adrese: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Razlog: {{reason}}",
|
"check_reason": "Razlog: {{reason}}",
|
||||||
"check_rule": "Pravilo: {{rule}}",
|
|
||||||
"check_service": "Naziv usluge: {{service}}",
|
"check_service": "Naziv usluge: {{service}}",
|
||||||
"service_name": "Naziv usluge",
|
"service_name": "Naziv usluge",
|
||||||
"check_not_found": "Nije pronađeno na vašoj listi filtara",
|
"check_not_found": "Nije pronađeno na vašoj listi filtara",
|
||||||
|
|||||||
@@ -269,7 +269,6 @@
|
|||||||
"source_label": "Forrás",
|
"source_label": "Forrás",
|
||||||
"found_in_known_domain_db": "Benne van az ismert domainek listájában.",
|
"found_in_known_domain_db": "Benne van az ismert domainek listájában.",
|
||||||
"category_label": "Kategória",
|
"category_label": "Kategória",
|
||||||
"rule_label": "Szabály",
|
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Ismeretlen szűrő: {{filterId}}",
|
"unknown_filter": "Ismeretlen szűrő: {{filterId}}",
|
||||||
"known_tracker": "Ismert követő",
|
"known_tracker": "Ismert követő",
|
||||||
@@ -330,7 +329,6 @@
|
|||||||
"encryption_config_saved": "Titkosítási beállítások mentve",
|
"encryption_config_saved": "Titkosítási beállítások mentve",
|
||||||
"encryption_server": "Szerver neve",
|
"encryption_server": "Szerver neve",
|
||||||
"encryption_server_enter": "Adja meg az Ön domain címét",
|
"encryption_server_enter": "Adja meg az Ön domain címét",
|
||||||
"encryption_server_desc": "A HTTPS használatához be kell írnia egy, az SSL-tanúsítvánnyal megegyező kiszolgálónevet.",
|
|
||||||
"encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra",
|
"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_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": "HTTPS port",
|
||||||
@@ -386,7 +384,6 @@
|
|||||||
"client_edit": "Kliens módosítása",
|
"client_edit": "Kliens módosítása",
|
||||||
"client_identifier": "Azonosító",
|
"client_identifier": "Azonosító",
|
||||||
"ip_address": "IP cím",
|
"ip_address": "IP cím",
|
||||||
"client_identifier_desc": "A klienseket be lehet azonosítani IP-cím, CIDR, valamint MAC-cím alapján. Kérjük, vegye figyelembe, hogy a MAC-cím alapján történő azonosítás csak akkor működik, ha az AdGuard Home egyben <0>DHCP szerverként</0> is funkcionál",
|
|
||||||
"form_enter_ip": "IP-cím megadása",
|
"form_enter_ip": "IP-cím megadása",
|
||||||
"form_enter_mac": "MAC-cím megadása",
|
"form_enter_mac": "MAC-cím megadása",
|
||||||
"form_enter_id": "Azonosító megadása",
|
"form_enter_id": "Azonosító megadása",
|
||||||
@@ -529,7 +526,6 @@
|
|||||||
"check_ip": "IP-címek: {{ip}}",
|
"check_ip": "IP-címek: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Indok: {{reason}}",
|
"check_reason": "Indok: {{reason}}",
|
||||||
"check_rule": "Szabály: {{rule}}",
|
|
||||||
"check_service": "Szolgáltatás neve: {{service}}",
|
"check_service": "Szolgáltatás neve: {{service}}",
|
||||||
"service_name": "Szolgáltatás neve",
|
"service_name": "Szolgáltatás neve",
|
||||||
"check_not_found": "Nem található az Ön szűrőlistái között",
|
"check_not_found": "Nem található az Ön szűrőlistái között",
|
||||||
|
|||||||
@@ -269,7 +269,6 @@
|
|||||||
"source_label": "Sumber",
|
"source_label": "Sumber",
|
||||||
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
||||||
"category_label": "Kategori",
|
"category_label": "Kategori",
|
||||||
"rule_label": "Aturan",
|
|
||||||
"list_label": "Daftar",
|
"list_label": "Daftar",
|
||||||
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
||||||
"known_tracker": "Pelacak yang dikenal",
|
"known_tracker": "Pelacak yang dikenal",
|
||||||
@@ -330,7 +329,6 @@
|
|||||||
"encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
|
"encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
|
||||||
"encryption_server": "Nama server",
|
"encryption_server": "Nama server",
|
||||||
"encryption_server_enter": "Masukkan nama domain anda",
|
"encryption_server_enter": "Masukkan nama domain anda",
|
||||||
"encryption_server_desc": "Untuk menggunakan HTTPS, Anda harus memasukkan nama server yang cocok dengan sertifikat SSL Anda.",
|
|
||||||
"encryption_redirect": "Alihkan ke HTTPS secara otomatis",
|
"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_redirect_desc": "Jika dicentang, AdGuard Home akan secara otomatis mengarahkan anda dari HTTP ke alamat HTTPS.",
|
||||||
"encryption_https": "Port HTTPS",
|
"encryption_https": "Port HTTPS",
|
||||||
@@ -386,7 +384,6 @@
|
|||||||
"client_edit": "Ubah Klien",
|
"client_edit": "Ubah Klien",
|
||||||
"client_identifier": "Identifikasi",
|
"client_identifier": "Identifikasi",
|
||||||
"ip_address": "Alamat IP",
|
"ip_address": "Alamat IP",
|
||||||
"client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP</0>",
|
|
||||||
"form_enter_ip": "Masukkan IP",
|
"form_enter_ip": "Masukkan IP",
|
||||||
"form_enter_mac": "Masukkan MAC",
|
"form_enter_mac": "Masukkan MAC",
|
||||||
"form_enter_id": "Masukkan pengenal",
|
"form_enter_id": "Masukkan pengenal",
|
||||||
@@ -529,7 +526,6 @@
|
|||||||
"check_ip": "Alamat IP: {{ip}}",
|
"check_ip": "Alamat IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Alasan: {{reason}}",
|
"check_reason": "Alasan: {{reason}}",
|
||||||
"check_rule": "Aturan: {{rule}}",
|
|
||||||
"check_service": "Nama layanan: {{service}}",
|
"check_service": "Nama layanan: {{service}}",
|
||||||
"service_name": "Nama layanan",
|
"service_name": "Nama layanan",
|
||||||
"check_not_found": "Tidak di temukan di daftar penyaringan anda",
|
"check_not_found": "Tidak di temukan di daftar penyaringan anda",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Formato IPv4 non valido",
|
"form_error_ip_format": "Formato IPv4 non valido",
|
||||||
"form_error_mac_format": "Formato MAC non valido",
|
"form_error_mac_format": "Formato MAC non valido",
|
||||||
"form_error_client_id_format": "Formato ID cliente non valido",
|
"form_error_client_id_format": "Formato ID cliente non valido",
|
||||||
|
"form_error_server_name": "Nome server non valido",
|
||||||
"form_error_positive": "Deve essere maggiore di 0",
|
"form_error_positive": "Deve essere maggiore di 0",
|
||||||
"form_error_negative": "Deve essere maggiore o uguale a 0 (zero)",
|
"form_error_negative": "Deve essere maggiore o uguale a 0 (zero)",
|
||||||
"range_end_error": "Deve essere maggiore dell'intervallo di inizio",
|
"range_end_error": "Deve essere maggiore dell'intervallo di inizio",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP personalizzato",
|
"custom_ip": "IP personalizzato",
|
||||||
"blocking_ipv4": "Blocca IPv4",
|
"blocking_ipv4": "Blocca IPv4",
|
||||||
"blocking_ipv6": "Blocca IPv6",
|
"blocking_ipv6": "Blocca IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS su HTTPS",
|
"dns_over_https": "DNS su HTTPS",
|
||||||
"dns_over_tls": "DNS su TLS",
|
"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. <a>Qui</a> potrai saperne di più sui metodi per identificarli.",
|
||||||
"download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS",
|
"download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS",
|
||||||
"download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS",
|
"download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS",
|
||||||
|
"download_mobileconfig": "Scarica file di configurazione",
|
||||||
"plain_dns": "DNS semplice",
|
"plain_dns": "DNS semplice",
|
||||||
"form_enter_rate_limit": "Imposta limite delle richieste",
|
"form_enter_rate_limit": "Imposta limite delle richieste",
|
||||||
"rate_limit": "Limite delle richieste",
|
"rate_limit": "Limite delle richieste",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Fonte",
|
"source_label": "Fonte",
|
||||||
"found_in_known_domain_db": "Trovato nel database dei domini conosciuti.",
|
"found_in_known_domain_db": "Trovato nel database dei domini conosciuti.",
|
||||||
"category_label": "Categoria",
|
"category_label": "Categoria",
|
||||||
"rule_label": "Regola",
|
"rule_label": "Regola(e)",
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Filtro sconosciuto {{filterId}}",
|
"unknown_filter": "Filtro sconosciuto {{filterId}}",
|
||||||
"known_tracker": "Tracker conosciuto",
|
"known_tracker": "Tracker conosciuto",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Configurazione della crittografia salvata",
|
"encryption_config_saved": "Configurazione della crittografia salvata",
|
||||||
"encryption_server": "Nome server",
|
"encryption_server": "Nome server",
|
||||||
"encryption_server_enter": "Inserisci il tuo nome di dominio",
|
"encryption_server_enter": "Inserisci il tuo nome di dominio",
|
||||||
"encryption_server_desc": "Per utilizzare HTTPS, è necessario inserire il nome del server che corrisponde al certificato SSL.",
|
"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_redirect": "Reindirizza automaticamente a HTTPS",
|
"encryption_redirect": "Reindirizza automaticamente a HTTPS",
|
||||||
"encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.",
|
"encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.",
|
||||||
"encryption_https": "Porta HTTPS",
|
"encryption_https": "Porta HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Modifica Client",
|
"client_edit": "Modifica Client",
|
||||||
"client_identifier": "Identificatore",
|
"client_identifier": "Identificatore",
|
||||||
"ip_address": "Indirizzo IP",
|
"ip_address": "Indirizzo IP",
|
||||||
"client_identifier_desc": "I client possono essere identificati dall indirizzo IP o dall' indirizzo MAC. Nota che l' utilizzo dell' indirizzo MAC come identificatore è consentito solo se AdGuard Home è anche il <0>server DHCP</0>",
|
"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</0> potrai saperne di più sui metodi per identificarli.",
|
||||||
"form_enter_ip": "Inserisci IP",
|
"form_enter_ip": "Inserisci IP",
|
||||||
"form_enter_mac": "Inserisci MAC",
|
"form_enter_mac": "Inserisci MAC",
|
||||||
"form_enter_id": "Inserisci identificatore",
|
"form_enter_id": "Inserisci identificatore",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supporta <1>DNS su HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supporta <1>DNS su HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supporta <1>DNS su HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supporta <1>DNS su HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Troverai più implementazioni <0>qui</0> e <1>qui</1>.",
|
"setup_dns_privacy_other_5": "Troverai più implementazioni <0>qui</0> e <1>qui</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "configurazione iOS e macOS",
|
||||||
"setup_dns_notice": "Per utilizzare <1>DNS su HTTPS</1> o <1>DNS su TLS</1>, è necessario <0>configurare la crittografia</0> nelle impostazioni di AdGuard Home.",
|
"setup_dns_notice": "Per utilizzare <1>DNS su HTTPS</1> o <1>DNS su TLS</1>, è necessario <0>configurare la crittografia</0> nelle impostazioni di AdGuard Home.",
|
||||||
"rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente",
|
"rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente",
|
||||||
"rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente",
|
"rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Indirizzi IP: {{ip}}",
|
"check_ip": "Indirizzi IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_rule": "Regola: {{rule}}",
|
|
||||||
"check_service": "Nome servizio: {{service}}",
|
"check_service": "Nome servizio: {{service}}",
|
||||||
"service_name": "Nome servizio",
|
"service_name": "Nome servizio",
|
||||||
"check_not_found": "Non trovato negli elenchi dei filtri",
|
"check_not_found": "Non trovato negli elenchi dei filtri",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "IPv4フォーマットではありません",
|
"form_error_ip_format": "IPv4フォーマットではありません",
|
||||||
"form_error_mac_format": "MACフォーマットではありません",
|
"form_error_mac_format": "MACフォーマットではありません",
|
||||||
"form_error_client_id_format": "Client IDの形式が無効です",
|
"form_error_client_id_format": "Client IDの形式が無効です",
|
||||||
|
"form_error_server_name": "サーバ名が無効です",
|
||||||
"form_error_positive": "0より大きい必要があります",
|
"form_error_positive": "0より大きい必要があります",
|
||||||
"form_error_negative": "0以上である必要があります",
|
"form_error_negative": "0以上である必要があります",
|
||||||
"range_end_error": "範囲開始よりも大きくなければなりません",
|
"range_end_error": "範囲開始よりも大きくなければなりません",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "カスタムIP",
|
"custom_ip": "カスタムIP",
|
||||||
"blocking_ipv4": "ブロック中のIPv4",
|
"blocking_ipv4": "ブロック中のIPv4",
|
||||||
"blocking_ipv6": "ブロック中のIPv6",
|
"blocking_ipv6": "ブロック中のIPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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で識別できます。 <a>ここ</a>では、クライアントを特定する方法について詳しく知ることができます。",
|
||||||
"download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード",
|
"download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード",
|
||||||
"download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード",
|
"download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード",
|
||||||
|
"download_mobileconfig": "設定ファイルをダウンロードする",
|
||||||
"plain_dns": "通常のDNS",
|
"plain_dns": "通常のDNS",
|
||||||
"form_enter_rate_limit": "頻度制限を入力してください",
|
"form_enter_rate_limit": "頻度制限を入力してください",
|
||||||
"rate_limit": "頻度制限",
|
"rate_limit": "頻度制限",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "暗号化の設定を保存しました",
|
"encryption_config_saved": "暗号化の設定を保存しました",
|
||||||
"encryption_server": "サーバ名",
|
"encryption_server": "サーバ名",
|
||||||
"encryption_server_enter": "ドメイン名を入力してください",
|
"encryption_server_enter": "ドメイン名を入力してください",
|
||||||
"encryption_server_desc": "HTTPSを使用するには、SSL証明書と一致するサーバ名を入力する必要があります。",
|
"encryption_server_desc": "HTTPSを使用するには、SSL証明書またはワイルドカード証明書と一致するサーバー名を入力する必要があります。このフィールドが設定されていない場合は、任意のドメインのTLS接続を受け入れます。",
|
||||||
"encryption_redirect": "HTTPSに自動的にリダイレクト",
|
"encryption_redirect": "HTTPSに自動的にリダイレクト",
|
||||||
"encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。",
|
"encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。",
|
||||||
"encryption_https": "HTTPS ポート",
|
"encryption_https": "HTTPS ポート",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "クライアントの編集",
|
"client_edit": "クライアントの編集",
|
||||||
"client_identifier": "識別子",
|
"client_identifier": "識別子",
|
||||||
"ip_address": "IPアドレス",
|
"ip_address": "IPアドレス",
|
||||||
"client_identifier_desc": "クライアントはIPアドレスまたはMACアドレスで識別できます。AdGuard Homeが<0>DHCPサーバ</0>でもある場合にのみ、識別子としてMACを使用することが可能であることにご注意ください。",
|
"client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、または特別なクライアントID(DoT/DoH/DoQで使用可能)によって識別することができます。<0>ここ</0>では、クライアントの識別方法についてより詳しく説明しています。",
|
||||||
"form_enter_ip": "IPアドレスを入力してください",
|
"form_enter_ip": "IPアドレスを入力してください",
|
||||||
"form_enter_mac": "MACアドレスを入力してください",
|
"form_enter_mac": "MACアドレスを入力してください",
|
||||||
"form_enter_id": "識別子を入力してください",
|
"form_enter_id": "識別子を入力してください",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0>は<1>DNS-over-HTTPS</1>をサポートします。",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0>は<1>DNS-over-HTTPS</1>をサポートします。",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0>は<1>DNS-over-HTTPS</1>をサポートしています。",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0>は<1>DNS-over-HTTPS</1>をサポートしています。",
|
||||||
"setup_dns_privacy_other_5": "もっと多くの実装を<0>ここ</0>や<1>ここ</1>で見つけられます。",
|
"setup_dns_privacy_other_5": "もっと多くの実装を<0>ここ</0>や<1>ここ</1>で見つけられます。",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS と macOS での設定",
|
||||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1>または<1>DNS-over-TLS</1>を使用するには、AdGuard Home 設定の<0>暗号化設定</0>が必要です。",
|
"setup_dns_notice": "<1>DNS-over-HTTPS</1>または<1>DNS-over-TLS</1>を使用するには、AdGuard Home 設定の<0>暗号化設定</0>が必要です。",
|
||||||
"rewrite_added": "\"{{key}}\" のためのDNS書き換え情報を追加完了しました",
|
"rewrite_added": "\"{{key}}\" のためのDNS書き換え情報を追加完了しました",
|
||||||
"rewrite_deleted": "\"{{key}}\" のためのDNS書き換え情報を削除完了しました",
|
"rewrite_deleted": "\"{{key}}\" のためのDNS書き換え情報を削除完了しました",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IPアドレス: {{ip}}",
|
"check_ip": "IPアドレス: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "理由: {{reason}}",
|
"check_reason": "理由: {{reason}}",
|
||||||
"check_rule": "ルール: {{rule}}",
|
|
||||||
"check_service": "サービス名: {{service}}",
|
"check_service": "サービス名: {{service}}",
|
||||||
"service_name": "サービス名",
|
"service_name": "サービス名",
|
||||||
"check_not_found": "フィルタ一覧には見つかりません",
|
"check_not_found": "フィルタ一覧には見つかりません",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "잘못된 IP 형식",
|
"form_error_ip_format": "잘못된 IP 형식",
|
||||||
"form_error_mac_format": "잘못된 MAC 형식",
|
"form_error_mac_format": "잘못된 MAC 형식",
|
||||||
"form_error_client_id_format": "잘못된 클라이언트 ID 형식",
|
"form_error_client_id_format": "잘못된 클라이언트 ID 형식",
|
||||||
|
"form_error_server_name": "유효하지 않은 서버 이름입니다",
|
||||||
"form_error_positive": "0보다 커야 합니다",
|
"form_error_positive": "0보다 커야 합니다",
|
||||||
"form_error_negative": "반드시 0 이상이여야 합니다",
|
"form_error_negative": "반드시 0 이상이여야 합니다",
|
||||||
"range_end_error": "입력 값은 범위의 시작 지점보다 큰 값 이여야 합니다.",
|
"range_end_error": "입력 값은 범위의 시작 지점보다 큰 값 이여야 합니다.",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "사용자 지정 IP",
|
"custom_ip": "사용자 지정 IP",
|
||||||
"blocking_ipv4": "IPv4 차단",
|
"blocking_ipv4": "IPv4 차단",
|
||||||
"blocking_ipv6": "IPv6 차단",
|
"blocking_ipv6": "IPv6 차단",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "클라이언트 ID",
|
||||||
|
"client_id_placeholder": "클라이언트 ID 입력",
|
||||||
|
"client_id_desc": "클라이언트는 특별한 클라이언트 ID를 기반으로 구분됩니다. <a>여기</a>에서 클라이언트를 구분하는 방법을 자세히 알아보세요.",
|
||||||
"download_mobileconfig_doh": "DNS-over-HTTPS용 .mobileconfig 다운로드",
|
"download_mobileconfig_doh": "DNS-over-HTTPS용 .mobileconfig 다운로드",
|
||||||
"download_mobileconfig_dot": "DNS-over-TLS용 .mobileconfig 다운로드",
|
"download_mobileconfig_dot": "DNS-over-TLS용 .mobileconfig 다운로드",
|
||||||
|
"download_mobileconfig": "설정 파일 내려받기",
|
||||||
"plain_dns": "평문 DNS",
|
"plain_dns": "평문 DNS",
|
||||||
"form_enter_rate_limit": "한도 제한 입력하기",
|
"form_enter_rate_limit": "한도 제한 입력하기",
|
||||||
"rate_limit": "한도 제한",
|
"rate_limit": "한도 제한",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "클라이언트 수정",
|
"client_edit": "클라이언트 수정",
|
||||||
"client_identifier": "식별자",
|
"client_identifier": "식별자",
|
||||||
"ip_address": "IP 주소",
|
"ip_address": "IP 주소",
|
||||||
"client_identifier_desc": "사용자는 IP 주소 또는 MAC 주소로 식별할 수 있지만 AdGuard Home이 <0>DHCP 서버인 </0> 경우에만 사용자는 MAC 주소로 식별할 수 있습니다.",
|
"client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 특수 클라이언트 ID로 식별할 수 있습니다 (DoT/DoH/DoQ에 사용 가능). <0>여기에서</0> 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.",
|
||||||
"form_enter_ip": "IP 입력",
|
"form_enter_ip": "IP 입력",
|
||||||
"form_enter_mac": "MAC 입력",
|
"form_enter_mac": "MAC 입력",
|
||||||
"form_enter_id": "식별자 입력",
|
"form_enter_id": "식별자 입력",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> <1>DNS-over-HTTPS</1> 지원합니다.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> <1>DNS-over-HTTPS</1> 지원합니다.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0><1>DNS-over-HTTPS</1>지원합니다.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0><1>DNS-over-HTTPS</1>지원합니다.",
|
||||||
"setup_dns_privacy_other_5": "<0>이곳이나</0> <1>이곳을</1> 클릭하여 더 많은 구현에 대한 정보를 확인하세요.",
|
"setup_dns_privacy_other_5": "<0>이곳이나</0> <1>이곳을</1> 클릭하여 더 많은 구현에 대한 정보를 확인하세요.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS 및 macOS 설정",
|
||||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> 또는 <1>DNS-over-TLS를</1> 사용하려면 AdGuard Home 설정에서 <0>암호화를 구성해야 합니다.</0>",
|
"setup_dns_notice": "<1>DNS-over-HTTPS</1> 또는 <1>DNS-over-TLS를</1> 사용하려면 AdGuard Home 설정에서 <0>암호화를 구성해야 합니다.</0>",
|
||||||
"rewrite_added": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 추가 됩니다.",
|
"rewrite_added": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 추가 됩니다.",
|
||||||
"rewrite_deleted": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 삭제 됩니다.",
|
"rewrite_deleted": "\"{{key}}\"에 대한 DNS 수정 정보를 성공적으로 삭제 됩니다.",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP 주소: {{ip}}",
|
"check_ip": "IP 주소: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "이유: {{reason}}",
|
"check_reason": "이유: {{reason}}",
|
||||||
"check_rule": "규칙: {{rule}}",
|
|
||||||
"check_service": "서비스 이름: {{service}}",
|
"check_service": "서비스 이름: {{service}}",
|
||||||
"service_name": "서비스 이름",
|
"service_name": "서비스 이름",
|
||||||
"check_not_found": "필터 목록에서 찾을 수 없음",
|
"check_not_found": "필터 목록에서 찾을 수 없음",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Ongeldig IPv4 formaat",
|
"form_error_ip_format": "Ongeldig IPv4 formaat",
|
||||||
"form_error_mac_format": "Ongeldig MAC formaat.",
|
"form_error_mac_format": "Ongeldig MAC formaat.",
|
||||||
"form_error_client_id_format": "Opmaak cliënt-ID is ongeldig",
|
"form_error_client_id_format": "Opmaak cliënt-ID is ongeldig",
|
||||||
|
"form_error_server_name": "Ongeldige servernaam",
|
||||||
"form_error_positive": "Moet groter zijn dan 0",
|
"form_error_positive": "Moet groter zijn dan 0",
|
||||||
"form_error_negative": "Moet 0 of hoger dan 0 zijn",
|
"form_error_negative": "Moet 0 of hoger dan 0 zijn",
|
||||||
"range_end_error": "Moet groter zijn dan het startbereik",
|
"range_end_error": "Moet groter zijn dan het startbereik",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Aangepast IP",
|
"custom_ip": "Aangepast IP",
|
||||||
"blocking_ipv4": "Blokkeren IP4",
|
"blocking_ipv4": "Blokkeren IP4",
|
||||||
"blocking_ipv6": "Blokkeren IP6",
|
"blocking_ipv6": "Blokkeren IP6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-via-HTTPS",
|
"dns_over_https": "DNS-via-HTTPS",
|
||||||
"dns_over_tls": "DNS-via-TLS",
|
"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. <a>Hier</a> vind je meer informatie over het identificeren van apparaten.",
|
||||||
"download_mobileconfig_doh": ".mobileconfig voor DNS-via-HTTPS downloaden",
|
"download_mobileconfig_doh": ".mobileconfig voor DNS-via-HTTPS downloaden",
|
||||||
"download_mobileconfig_dot": ".mobileconfig voor DNS-via-TLS downloaden",
|
"download_mobileconfig_dot": ".mobileconfig voor DNS-via-TLS downloaden",
|
||||||
|
"download_mobileconfig": "Configuratiebestand downloaden",
|
||||||
"plain_dns": "Gewone DNS",
|
"plain_dns": "Gewone DNS",
|
||||||
"form_enter_rate_limit": "Voer ratio limiet in",
|
"form_enter_rate_limit": "Voer ratio limiet in",
|
||||||
"rate_limit": "Ratio limiet",
|
"rate_limit": "Ratio limiet",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Bron",
|
"source_label": "Bron",
|
||||||
"found_in_known_domain_db": "Gevonden in de bekende domeingegevensbank.",
|
"found_in_known_domain_db": "Gevonden in de bekende domeingegevensbank.",
|
||||||
"category_label": "Categorie",
|
"category_label": "Categorie",
|
||||||
"rule_label": "Regel",
|
"rule_label": "Regel(s)",
|
||||||
"list_label": "Lijst",
|
"list_label": "Lijst",
|
||||||
"unknown_filter": "Onbekend filter {{filterId}}",
|
"unknown_filter": "Onbekend filter {{filterId}}",
|
||||||
"known_tracker": "Bekende volger",
|
"known_tracker": "Bekende volger",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Encryptie configuratie opgeslagen",
|
"encryption_config_saved": "Encryptie configuratie opgeslagen",
|
||||||
"encryption_server": "Server naam",
|
"encryption_server": "Server naam",
|
||||||
"encryption_server_enter": "Voer domein naam in",
|
"encryption_server_enter": "Voer domein naam in",
|
||||||
"encryption_server_desc": "Om HTTPS te gebruiken, voer de naam in van de server overeenkomstig met het SSL certificaat.",
|
"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_redirect": "Herleid automatisch naar HTTPS",
|
"encryption_redirect": "Herleid automatisch naar HTTPS",
|
||||||
"encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.",
|
"encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.",
|
||||||
"encryption_https": "HTTPS poort",
|
"encryption_https": "HTTPS poort",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Wijzig gebruiker",
|
"client_edit": "Wijzig gebruiker",
|
||||||
"client_identifier": "Identificeer via",
|
"client_identifier": "Identificeer via",
|
||||||
"ip_address": "IP adres",
|
"ip_address": "IP adres",
|
||||||
"client_identifier_desc": "Gebruikers kunnen worden geïdentificeerd door het IP-adres, CIDR of MAC-adres. Hou er rekening mee dat het gebruik van MAC als ID alleen mogelijk is als AdGuard Home ook een <0>DHCP-server</0> is",
|
"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</0> kan je meer lezen over het identificeren van apparaten.",
|
||||||
"form_enter_ip": "Vul IP in",
|
"form_enter_ip": "Vul IP in",
|
||||||
"form_enter_mac": "Vul MAC in",
|
"form_enter_mac": "Vul MAC in",
|
||||||
"form_enter_id": "ID invoeren",
|
"form_enter_id": "ID invoeren",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> ondersteunt <1>DNS-via-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> ondersteunt <1>DNS-via-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> ondersteunt <1>DNS-via-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> ondersteunt <1>DNS-via-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "U vindt meer implementaties <0> hier </0> en <1> hier </1>.",
|
"setup_dns_privacy_other_5": "U vindt meer implementaties <0> hier </0> en <1> hier </1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS en macOS configuratie",
|
||||||
"setup_dns_notice": "Om <1>DNS-via-HTTPS</1> of <1>DNS-via-TLS</1> te gebruiken, moet je <0>Versleuteling configureren</0> in de AdGuard Home instellingen.",
|
"setup_dns_notice": "Om <1>DNS-via-HTTPS</1> of <1>DNS-via-TLS</1> te gebruiken, moet je <0>Versleuteling configureren</0> in de AdGuard Home instellingen.",
|
||||||
"rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd",
|
"rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd",
|
||||||
"rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd",
|
"rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP-adressen: {{ip}}",
|
"check_ip": "IP-adressen: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Reden: {{reason}}",
|
"check_reason": "Reden: {{reason}}",
|
||||||
"check_rule": "Regel: {{rule}}",
|
|
||||||
"check_service": "Servicenaam: {{service}}",
|
"check_service": "Servicenaam: {{service}}",
|
||||||
"service_name": "Naam service",
|
"service_name": "Naam service",
|
||||||
"check_not_found": "Niet in je lijst met filters gevonden",
|
"check_not_found": "Niet in je lijst met filters gevonden",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Ugyldig IPv4-format",
|
"form_error_ip_format": "Ugyldig IPv4-format",
|
||||||
"form_error_mac_format": "Ugyldig MAC-format",
|
"form_error_mac_format": "Ugyldig MAC-format",
|
||||||
"form_error_client_id_format": "Ugyldig ID-klientformat",
|
"form_error_client_id_format": "Ugyldig ID-klientformat",
|
||||||
|
"form_error_server_name": "Ugyldig tjenernavn",
|
||||||
"form_error_positive": "Må være høyere enn 0",
|
"form_error_positive": "Må være høyere enn 0",
|
||||||
"form_error_negative": "Må være ≥0",
|
"form_error_negative": "Må være ≥0",
|
||||||
"range_end_error": "Må være høyere enn rekkeviddens start",
|
"range_end_error": "Må være høyere enn rekkeviddens start",
|
||||||
@@ -247,8 +248,11 @@
|
|||||||
"custom_ip": "Tilpasset IP",
|
"custom_ip": "Tilpasset IP",
|
||||||
"blocking_ipv4": "IPv4-blokkering",
|
"blocking_ipv4": "IPv4-blokkering",
|
||||||
"blocking_ipv6": "IPv6-blokkering",
|
"blocking_ipv6": "IPv6-blokkering",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "Klient-ID",
|
||||||
"download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS",
|
"download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS",
|
||||||
"plain_dns": "Ordinær DNS",
|
"plain_dns": "Ordinær DNS",
|
||||||
@@ -269,7 +273,6 @@
|
|||||||
"source_label": "Kilde",
|
"source_label": "Kilde",
|
||||||
"found_in_known_domain_db": "Funnet i databasen over kjente domener.",
|
"found_in_known_domain_db": "Funnet i databasen over kjente domener.",
|
||||||
"category_label": "Kategori",
|
"category_label": "Kategori",
|
||||||
"rule_label": "Oppføring",
|
|
||||||
"list_label": "Liste",
|
"list_label": "Liste",
|
||||||
"unknown_filter": "Ukjent filter {{filterId}}",
|
"unknown_filter": "Ukjent filter {{filterId}}",
|
||||||
"known_tracker": "Kjent sporer",
|
"known_tracker": "Kjent sporer",
|
||||||
@@ -330,7 +333,6 @@
|
|||||||
"encryption_config_saved": "Krypteringsoppsettet ble lagret",
|
"encryption_config_saved": "Krypteringsoppsettet ble lagret",
|
||||||
"encryption_server": "Tjenerens navn",
|
"encryption_server": "Tjenerens navn",
|
||||||
"encryption_server_enter": "Skriv inn domenenavnet ditt",
|
"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.",
|
|
||||||
"encryption_redirect": "Automatisk omdiriger til HTTPS",
|
"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_redirect_desc": "Dersom dette er valgt, vil AdGuard Home automatisk omdirigere deg fra HTTP til HTTPS-adresser.",
|
||||||
"encryption_https": "HTTPS-port",
|
"encryption_https": "HTTPS-port",
|
||||||
@@ -386,7 +388,6 @@
|
|||||||
"client_edit": "Rediger klienten",
|
"client_edit": "Rediger klienten",
|
||||||
"client_identifier": "Identifikator",
|
"client_identifier": "Identifikator",
|
||||||
"ip_address": "IP-adresse",
|
"ip_address": "IP-adresse",
|
||||||
"client_identifier_desc": "Klienter kan bli identifisert gjennom IP-adressen eller MAC-adressen. Vennligst bemerk at å bruke MAC som en identifikator, bare er mulig dersom AdGuard Home også er en <0>DHCP-tjener</0>",
|
|
||||||
"form_enter_ip": "Skriv inn IP",
|
"form_enter_ip": "Skriv inn IP",
|
||||||
"form_enter_mac": "Skriv inn MAC",
|
"form_enter_mac": "Skriv inn MAC",
|
||||||
"form_enter_id": "Skriv inn identifikator",
|
"form_enter_id": "Skriv inn identifikator",
|
||||||
@@ -528,7 +529,6 @@
|
|||||||
"check_ip": "IP-adresser: {{ip}}",
|
"check_ip": "IP-adresser: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Årsak: {{reason}}",
|
"check_reason": "Årsak: {{reason}}",
|
||||||
"check_rule": "Oppføring: {{rule}}",
|
|
||||||
"check_service": "Tjenestenavn: {{service}}",
|
"check_service": "Tjenestenavn: {{service}}",
|
||||||
"service_name": "Tjenestenavn",
|
"service_name": "Tjenestenavn",
|
||||||
"check_not_found": "Ikke funnet i filterlistene dine",
|
"check_not_found": "Ikke funnet i filterlistene dine",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Nieprawidłowy format IP",
|
"form_error_ip_format": "Nieprawidłowy format IP",
|
||||||
"form_error_mac_format": "Nieprawidłowy format MAC",
|
"form_error_mac_format": "Nieprawidłowy format MAC",
|
||||||
"form_error_client_id_format": "Nieprawidłowy format identyfikatora klienta",
|
"form_error_client_id_format": "Nieprawidłowy format identyfikatora klienta",
|
||||||
|
"form_error_server_name": "Nieprawidłowa nazwa serwera",
|
||||||
"form_error_positive": "Musi być większa niż 0",
|
"form_error_positive": "Musi być większa niż 0",
|
||||||
"form_error_negative": "Musi być równy 0 lub większy",
|
"form_error_negative": "Musi być równy 0 lub większy",
|
||||||
"range_end_error": "Zakres musi być większy niż początkowy",
|
"range_end_error": "Zakres musi być większy niż początkowy",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Niestandardowy adres IP",
|
"custom_ip": "Niestandardowy adres IP",
|
||||||
"blocking_ipv4": "Blokowanie IPv4",
|
"blocking_ipv4": "Blokowanie IPv4",
|
||||||
"blocking_ipv6": "Blokowanie IPv6",
|
"blocking_ipv6": "Blokowanie IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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. <a>Tutaj</a> możesz dowiedzieć się więcej o tym, jak identyfikować klientów.",
|
||||||
"download_mobileconfig_doh": "Pobierz plik .mobileconfig dla DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Pobierz plik .mobileconfig dla DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Pobierz plik .mobileconfig dla DNS-over-TLS",
|
"download_mobileconfig_dot": "Pobierz plik .mobileconfig dla DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Pobierz plik konfiguracyjny",
|
||||||
"plain_dns": "Zwykły DNS",
|
"plain_dns": "Zwykły DNS",
|
||||||
"form_enter_rate_limit": "Wpisz limit ilościowy",
|
"form_enter_rate_limit": "Wpisz limit ilościowy",
|
||||||
"rate_limit": "Limit ilościowy",
|
"rate_limit": "Limit ilościowy",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Źródło",
|
"source_label": "Źródło",
|
||||||
"found_in_known_domain_db": "Znaleziono w bazie danych znanych domen.",
|
"found_in_known_domain_db": "Znaleziono w bazie danych znanych domen.",
|
||||||
"category_label": "Kategoria",
|
"category_label": "Kategoria",
|
||||||
"rule_label": "Reguła",
|
"rule_label": "Reguła(y)",
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Nieznany filtr {{filterId}}",
|
"unknown_filter": "Nieznany filtr {{filterId}}",
|
||||||
"known_tracker": "Znany element śledzący",
|
"known_tracker": "Znany element śledzący",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Zapisano konfigurację szyfrowania",
|
"encryption_config_saved": "Zapisano konfigurację szyfrowania",
|
||||||
"encryption_server": "Nazwa serwera",
|
"encryption_server": "Nazwa serwera",
|
||||||
"encryption_server_enter": "Wpisz swoją nazwę domeny",
|
"encryption_server_enter": "Wpisz swoją nazwę domeny",
|
||||||
"encryption_server_desc": "Aby korzystać z protokołu HTTPS, musisz wprowadzić nazwę serwera zgodną z certyfikatem SSL.",
|
"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_redirect": "Przekieruj automatycznie do HTTPS",
|
"encryption_redirect": "Przekieruj automatycznie do HTTPS",
|
||||||
"encryption_redirect_desc": "Jeśli zaznaczone, AdGuard Home automatycznie przekieruje Cię z adresów HTTP na HTTPS.",
|
"encryption_redirect_desc": "Jeśli zaznaczone, AdGuard Home automatycznie przekieruje Cię z adresów HTTP na HTTPS.",
|
||||||
"encryption_https": "Port HTTPS",
|
"encryption_https": "Port HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Edytuj klienta",
|
"client_edit": "Edytuj klienta",
|
||||||
"client_identifier": "Identyfikator",
|
"client_identifier": "Identyfikator",
|
||||||
"ip_address": "Adres IP",
|
"ip_address": "Adres IP",
|
||||||
"client_identifier_desc": "Klienci mogą być identyfikowani na podstawie adresu IP, CIDR, adresu MAC. Pamiętaj, że użycie MAC jako identyfikatora jest możliwe tylko wtedy, gdy AdGuard Home jest również <0>serwerem DHCP</0>",
|
"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</0> możesz dowiedzieć się więcej o tym, jak identyfikować klientów.",
|
||||||
"form_enter_ip": "Wpisz adres IP",
|
"form_enter_ip": "Wpisz adres IP",
|
||||||
"form_enter_mac": "Wpisz adres MAC",
|
"form_enter_mac": "Wpisz adres MAC",
|
||||||
"form_enter_id": "Wpisz identyfikator",
|
"form_enter_id": "Wpisz identyfikator",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.",
|
"setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Konfiguracja iOS i macOS",
|
||||||
"setup_dns_notice": "Aby skorzystać z <1>DNS-over-HTTPS</1> lub <1>DNS-over-TLS</1>, musisz w ustawieniach AdGuard Home <0>skonfigurować szyfrowanie</0>.",
|
"setup_dns_notice": "Aby skorzystać z <1>DNS-over-HTTPS</1> lub <1>DNS-over-TLS</1>, musisz w ustawieniach AdGuard Home <0>skonfigurować szyfrowanie</0>.",
|
||||||
"rewrite_added": "Pomyślnie dodano przepisanie DNS dla „{{key}}”",
|
"rewrite_added": "Pomyślnie dodano przepisanie DNS dla „{{key}}”",
|
||||||
"rewrite_deleted": "Przepisanie DNS dla „{{key}}” zostało pomyślnie usunięte",
|
"rewrite_deleted": "Przepisanie DNS dla „{{key}}” zostało pomyślnie usunięte",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Adresy IP: {{ip}}",
|
"check_ip": "Adresy IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Powód: {{reason}}",
|
"check_reason": "Powód: {{reason}}",
|
||||||
"check_rule": "Reguła: {{rule}}",
|
|
||||||
"check_service": "Nazwa usługi: {{service}}",
|
"check_service": "Nazwa usługi: {{service}}",
|
||||||
"service_name": "Nazwa usługi",
|
"service_name": "Nazwa usługi",
|
||||||
"check_not_found": "Nie znaleziono na Twoich listach filtrów",
|
"check_not_found": "Nie znaleziono na Twoich listach filtrów",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Formato de endereço IPv inválido",
|
"form_error_ip_format": "Formato de endereço IPv inválido",
|
||||||
"form_error_mac_format": "Formato do endereço MAC inválido",
|
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||||
"form_error_client_id_format": "Formato do ID de cliente inválido",
|
"form_error_client_id_format": "Formato do ID de cliente inválido",
|
||||||
|
"form_error_server_name": "Nome de servidor inválido",
|
||||||
"form_error_positive": "Deve ser maior que 0",
|
"form_error_positive": "Deve ser maior que 0",
|
||||||
"form_error_negative": "Deve ser igual ou superior a 0",
|
"form_error_negative": "Deve ser igual ou superior a 0",
|
||||||
"range_end_error": "Deve ser maior que o início do intervalo",
|
"range_end_error": "Deve ser maior que o início do intervalo",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP personalizado",
|
"custom_ip": "IP personalizado",
|
||||||
"blocking_ipv4": "Bloqueando IPv4",
|
"blocking_ipv4": "Bloqueando IPv4",
|
||||||
"blocking_ipv6": "Bloqueando IPv6",
|
"blocking_ipv6": "Bloqueando IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-sobre-HTTPS",
|
"dns_over_https": "DNS-sobre-HTTPS",
|
||||||
"dns_over_tls": "DNS-sobre-TLS",
|
"dns_over_tls": "DNS-sobre-TLS",
|
||||||
|
"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. <a>Aqui</a> você pode aprender mais sobre como identificar clientes.",
|
||||||
"download_mobileconfig_doh": "BAixar .mobileconfig para DNS-sobre-HTTPS",
|
"download_mobileconfig_doh": "BAixar .mobileconfig para DNS-sobre-HTTPS",
|
||||||
"download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS",
|
"download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS",
|
||||||
|
"download_mobileconfig": "Baixar arquivo de configuração",
|
||||||
"plain_dns": "DNS simples",
|
"plain_dns": "DNS simples",
|
||||||
"form_enter_rate_limit": "Insira a taxa limite",
|
"form_enter_rate_limit": "Insira a taxa limite",
|
||||||
"rate_limit": "Taxa limite",
|
"rate_limit": "Taxa limite",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Fonte",
|
"source_label": "Fonte",
|
||||||
"found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecidos.",
|
"found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecidos.",
|
||||||
"category_label": "Categoria",
|
"category_label": "Categoria",
|
||||||
"rule_label": "Regra",
|
"rule_label": "Regra(s)",
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
||||||
"known_tracker": "Rastreador conhecido",
|
"known_tracker": "Rastreador conhecido",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Configuração de criptografia salva",
|
"encryption_config_saved": "Configuração de criptografia salva",
|
||||||
"encryption_server": "Nome do servidor",
|
"encryption_server": "Nome do servidor",
|
||||||
"encryption_server_enter": "Digite seu nome de domínio",
|
"encryption_server_enter": "Digite seu nome de domínio",
|
||||||
"encryption_server_desc": "Para usar o protocolo HTTPS, você precisa digitar o nome do servidor que corresponde ao seu certificado SSL.",
|
"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_redirect": "Redirecionar automaticamente para HTTPS",
|
"encryption_redirect": "Redirecionar automaticamente para HTTPS",
|
||||||
"encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.",
|
"encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.",
|
||||||
"encryption_https": "Porta HTTPS",
|
"encryption_https": "Porta HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Editar cliente",
|
"client_edit": "Editar cliente",
|
||||||
"client_identifier": "Identificador",
|
"client_identifier": "Identificador",
|
||||||
"ip_address": "Endereço de IP",
|
"ip_address": "Endereço de IP",
|
||||||
"client_identifier_desc": "Clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
"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</0> você pode aprender mais sobre como identificar clientes.",
|
||||||
"form_enter_ip": "Digite o endereço de IP",
|
"form_enter_ip": "Digite o endereço de IP",
|
||||||
"form_enter_mac": "Digite o endereço MAC",
|
"form_enter_mac": "Digite o endereço MAC",
|
||||||
"form_enter_id": "Inserir identificador",
|
"form_enter_id": "Inserir identificador",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> suporta <1>DNS-sobre-HTTPS</1>",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> suporta <1>DNS-sobre-HTTPS</1>",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Você encontrará mais implementações <0>aqui</0> e <1>aqui</1>.",
|
"setup_dns_privacy_other_5": "Você encontrará mais implementações <0>aqui</0> e <1>aqui</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "configuração para iOS e macOS",
|
||||||
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, você precisa <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, você precisa <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
||||||
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
||||||
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Endereços de IP: {{ip}}",
|
"check_ip": "Endereços de IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_rule": "Regra: {{rule}}",
|
|
||||||
"check_service": "Nome do serviço: {{service}}",
|
"check_service": "Nome do serviço: {{service}}",
|
||||||
"service_name": "Nome do serviço",
|
"service_name": "Nome do serviço",
|
||||||
"check_not_found": "Não encontrado em suas listas de filtros",
|
"check_not_found": "Não encontrado em suas listas de filtros",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Formato de endereço IPv4 inválido",
|
"form_error_ip_format": "Formato de endereço IPv4 inválido",
|
||||||
"form_error_mac_format": "Formato do endereço MAC inválido",
|
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||||
"form_error_client_id_format": "Formato inválido",
|
"form_error_client_id_format": "Formato inválido",
|
||||||
|
"form_error_server_name": "Nome de servidor inválido",
|
||||||
"form_error_positive": "Deve ser maior que 0",
|
"form_error_positive": "Deve ser maior que 0",
|
||||||
"form_error_negative": "Deve ser igual ou superior a 0",
|
"form_error_negative": "Deve ser igual ou superior a 0",
|
||||||
"range_end_error": "Deve ser maior que o início do intervalo",
|
"range_end_error": "Deve ser maior que o início do intervalo",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP Personalizado",
|
"custom_ip": "IP Personalizado",
|
||||||
"blocking_ipv4": "A bloquear IPv4",
|
"blocking_ipv4": "A bloquear IPv4",
|
||||||
"blocking_ipv6": "A bloquear IPv6",
|
"blocking_ipv6": "A bloquear IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-sobre-HTTPS",
|
"dns_over_https": "DNS-sobre-HTTPS",
|
||||||
"dns_over_tls": "DNS-sobre-TLS",
|
"dns_over_tls": "DNS-sobre-TLS",
|
||||||
|
"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. <a>Aqui</a> você pode aprender mais sobre como identificar clientes.",
|
||||||
"download_mobileconfig_doh": "Transferir .mobileconfig para DNS-sobre-HTTPS",
|
"download_mobileconfig_doh": "Transferir .mobileconfig para DNS-sobre-HTTPS",
|
||||||
"download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS",
|
"download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS",
|
||||||
|
"download_mobileconfig": "Transferir arquivo de configuração",
|
||||||
"plain_dns": "DNS simples",
|
"plain_dns": "DNS simples",
|
||||||
"form_enter_rate_limit": "Insira o limite de taxa",
|
"form_enter_rate_limit": "Insira o limite de taxa",
|
||||||
"rate_limit": "Limite de taxa",
|
"rate_limit": "Limite de taxa",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Fonte",
|
"source_label": "Fonte",
|
||||||
"found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecido.",
|
"found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecido.",
|
||||||
"category_label": "Categoria",
|
"category_label": "Categoria",
|
||||||
"rule_label": "Regra",
|
"rule_label": "Regra(s)",
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
||||||
"known_tracker": "Rastreador conhecido",
|
"known_tracker": "Rastreador conhecido",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Definição de criptografia guardada",
|
"encryption_config_saved": "Definição de criptografia guardada",
|
||||||
"encryption_server": "Nome do servidor",
|
"encryption_server": "Nome do servidor",
|
||||||
"encryption_server_enter": "Insira o seu nome de domínio",
|
"encryption_server_enter": "Insira o seu nome de domínio",
|
||||||
"encryption_server_desc": "Para usar o protocolo HTTPS, precisa de inserir o nome do servidor que corresponde ao seu certificado SSL.",
|
"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_redirect": "Redireccionar automaticamente para HTTPS",
|
"encryption_redirect": "Redireccionar automaticamente para HTTPS",
|
||||||
"encryption_redirect_desc": "Se marcado, o AdGuard Home irá redireccionar automaticamente os endereços HTTP para HTTPS.",
|
"encryption_redirect_desc": "Se marcado, o AdGuard Home irá redireccionar automaticamente os endereços HTTP para HTTPS.",
|
||||||
"encryption_https": "Porta HTTPS",
|
"encryption_https": "Porta HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Editar cliente",
|
"client_edit": "Editar cliente",
|
||||||
"client_identifier": "Identificador",
|
"client_identifier": "Identificador",
|
||||||
"ip_address": "Endereço de IP",
|
"ip_address": "Endereço de IP",
|
||||||
"client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP, CIDR, ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
"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</0> você pode aprender mais sobre como identificar clientes.",
|
||||||
"form_enter_ip": "Insira IP",
|
"form_enter_ip": "Insira IP",
|
||||||
"form_enter_mac": "Insira o endereço MAC",
|
"form_enter_mac": "Insira o endereço MAC",
|
||||||
"form_enter_id": "Inserir identificador",
|
"form_enter_id": "Inserir identificador",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Encontrará mais implementações <0>aqui</0> e <1>aqui</1>.",
|
"setup_dns_privacy_other_5": "Encontrará mais implementações <0>aqui</0> e <1>aqui</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "configuração para iOS e macOS",
|
||||||
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, precisa de <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, precisa de <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
||||||
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
||||||
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Endereços de IP: {{ip}}",
|
"check_ip": "Endereços de IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_rule": "Regra: {{rule}}",
|
|
||||||
"check_service": "Nome do serviço: {{service}}",
|
"check_service": "Nome do serviço: {{service}}",
|
||||||
"service_name": "Nome do serviço",
|
"service_name": "Nome do serviço",
|
||||||
"check_not_found": "Não encontrado nas tuas listas de filtros",
|
"check_not_found": "Não encontrado nas tuas listas de filtros",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Format IP invalid",
|
"form_error_ip_format": "Format IP invalid",
|
||||||
"form_error_mac_format": "Format MAC invalid",
|
"form_error_mac_format": "Format MAC invalid",
|
||||||
"form_error_client_id_format": "Format ID de client invalid",
|
"form_error_client_id_format": "Format ID de client invalid",
|
||||||
|
"form_error_server_name": "Nume de server nevalid",
|
||||||
"form_error_positive": "Trebuie să fie mai mare de 0",
|
"form_error_positive": "Trebuie să fie mai mare de 0",
|
||||||
"form_error_negative": "Trebuie să fie egală cu 0 sau mai mare",
|
"form_error_negative": "Trebuie să fie egală cu 0 sau mai mare",
|
||||||
"range_end_error": "Trebuie să fie mai mare decât începutul intervalului",
|
"range_end_error": "Trebuie să fie mai mare decât începutul intervalului",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP personalizat",
|
"custom_ip": "IP personalizat",
|
||||||
"blocking_ipv4": "Blocarea IPv4",
|
"blocking_ipv4": "Blocarea IPv4",
|
||||||
"blocking_ipv6": "Blocarea IPv6",
|
"blocking_ipv6": "Blocarea IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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. <a>Aici</a> puteți afla mai multe despre cum să identificați clienții.",
|
||||||
"download_mobileconfig_doh": "Descărcați .mobileconfig pentru DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Descărcați .mobileconfig pentru DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Descărcați .mobileconfig pentru DNS-over-TLS",
|
"download_mobileconfig_dot": "Descărcați .mobileconfig pentru DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Descărcați fișierul de configurare",
|
||||||
"plain_dns": "DNS simplu",
|
"plain_dns": "DNS simplu",
|
||||||
"form_enter_rate_limit": "Introduceți limita ratei",
|
"form_enter_rate_limit": "Introduceți limita ratei",
|
||||||
"rate_limit": "Limita ratei",
|
"rate_limit": "Limita ratei",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Sursă",
|
"source_label": "Sursă",
|
||||||
"found_in_known_domain_db": "Găsit în baza de date de domenii cunoscută.",
|
"found_in_known_domain_db": "Găsit în baza de date de domenii cunoscută.",
|
||||||
"category_label": "Categorie",
|
"category_label": "Categorie",
|
||||||
"rule_label": "Regulă",
|
"rule_label": "Regulă(reguli)",
|
||||||
"list_label": "Listă",
|
"list_label": "Listă",
|
||||||
"unknown_filter": "Filtru necunoscut {{filterId}}",
|
"unknown_filter": "Filtru necunoscut {{filterId}}",
|
||||||
"known_tracker": "Tracker cunoscut",
|
"known_tracker": "Tracker cunoscut",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Configurația de criptare salvată",
|
"encryption_config_saved": "Configurația de criptare salvată",
|
||||||
"encryption_server": "Nume de server",
|
"encryption_server": "Nume de server",
|
||||||
"encryption_server_enter": "Introduceți numele domeniului",
|
"encryption_server_enter": "Introduceți numele domeniului",
|
||||||
"encryption_server_desc": "Pentru a utiliza HTTPS, trebuie introdus numele serverului care corespunde certificatului SSL.",
|
"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_redirect": "Redirecționați automat la HTTPS",
|
"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_redirect_desc": "Dacă este bifat, AdGuard Home vă va redirecționa automat de la adrese HTTP la HTTPS.",
|
||||||
"encryption_https": "Port HTTPS",
|
"encryption_https": "Port HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Editare client",
|
"client_edit": "Editare client",
|
||||||
"client_identifier": "Identificator",
|
"client_identifier": "Identificator",
|
||||||
"ip_address": "Adresa IP",
|
"ip_address": "Adresa IP",
|
||||||
"client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC. Vă rugăm să rețineți că utilizarea MAC ca identificator este posibilă numai dacă AdGuard Home este și un <0>server DHCP</0>",
|
"client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC sau un ID special al clientului (poate fi folosit pentru DoT/DoH/DoQ). <0>Aici</0> puteți afla mai multe despre cum să identificați clienții.",
|
||||||
"form_enter_ip": "Introduceți IP",
|
"form_enter_ip": "Introduceți IP",
|
||||||
"form_enter_mac": "Introduceți MAC",
|
"form_enter_mac": "Introduceți MAC",
|
||||||
"form_enter_id": "Introduceți identificator",
|
"form_enter_id": "Introduceți identificator",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> acceptă <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> acceptă <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> acceptă <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> acceptă <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Veți găsi mai multe implementări <0>aici</0> și <1>aici</1>.",
|
"setup_dns_privacy_other_5": "Veți găsi mai multe implementări <0>aici</0> și <1>aici</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Configurarea iOS și macOS",
|
||||||
"setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS</1> sau <1>DNS-over-TLS</1>, trebuie să <0>configurați Criptarea</0> în setările AdGuard Home.",
|
"setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS</1> sau <1>DNS-over-TLS</1>, trebuie să <0>configurați Criptarea</0> în setările AdGuard Home.",
|
||||||
"rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugată cu succes",
|
"rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugată cu succes",
|
||||||
"rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes",
|
"rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "Adrese IP: {{ip}}",
|
"check_ip": "Adrese IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Cauza: {{reason}}",
|
"check_reason": "Cauza: {{reason}}",
|
||||||
"check_rule": "Regula: {{rule}}",
|
|
||||||
"check_service": "Nume servici: {{service}}",
|
"check_service": "Nume servici: {{service}}",
|
||||||
"service_name": "Numele serviciului",
|
"service_name": "Numele serviciului",
|
||||||
"check_not_found": "Nu se găsește în listele de filtre",
|
"check_not_found": "Nu se găsește în listele de filtre",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Неверный формат IP-адреса",
|
"form_error_ip_format": "Неверный формат IP-адреса",
|
||||||
"form_error_mac_format": "Некорректный формат MAC",
|
"form_error_mac_format": "Некорректный формат MAC",
|
||||||
"form_error_client_id_format": "Неверный формат ID клиента",
|
"form_error_client_id_format": "Неверный формат ID клиента",
|
||||||
|
"form_error_server_name": "Неверное имя сервера",
|
||||||
"form_error_positive": "Должно быть больше 0",
|
"form_error_positive": "Должно быть больше 0",
|
||||||
"form_error_negative": "Должно быть не меньше 0",
|
"form_error_negative": "Должно быть не меньше 0",
|
||||||
"range_end_error": "Должно превышать начало диапазона",
|
"range_end_error": "Должно превышать начало диапазона",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Свой IP",
|
"custom_ip": "Свой IP",
|
||||||
"blocking_ipv4": "Блокировка IPv4",
|
"blocking_ipv4": "Блокировка IPv4",
|
||||||
"blocking_ipv6": "Блокировка IPv6",
|
"blocking_ipv6": "Блокировка IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "Идентификатор клиента",
|
||||||
|
"client_id_placeholder": "Введите идентификатор клиента",
|
||||||
|
"client_id_desc": "Различные клиенты могут идентифицироваться по специальному идентификатору клиента. <a>Здесь</a> вы можете узнать больше об идентификации клиентов.",
|
||||||
"download_mobileconfig_doh": "Скачать .mobileconfig для DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Скачать .mobileconfig для DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Скачать .mobileconfig для DNS-over-TLS",
|
"download_mobileconfig_dot": "Скачать .mobileconfig для DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Загрузить файл конфигурации",
|
||||||
"plain_dns": "Нешифрованный DNS",
|
"plain_dns": "Нешифрованный DNS",
|
||||||
"form_enter_rate_limit": "Введите rate limit",
|
"form_enter_rate_limit": "Введите rate limit",
|
||||||
"rate_limit": "Rate limit",
|
"rate_limit": "Rate limit",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Источник",
|
"source_label": "Источник",
|
||||||
"found_in_known_domain_db": "Найден в базе известных доменов.",
|
"found_in_known_domain_db": "Найден в базе известных доменов.",
|
||||||
"category_label": "Категория",
|
"category_label": "Категория",
|
||||||
"rule_label": "Правило",
|
"rule_label": "Правило(-а)",
|
||||||
"list_label": "Список",
|
"list_label": "Список",
|
||||||
"unknown_filter": "Неизвестный фильтр {{filterId}}",
|
"unknown_filter": "Неизвестный фильтр {{filterId}}",
|
||||||
"known_tracker": "Известный трекер",
|
"known_tracker": "Известный трекер",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Настройки шифрования сохранены",
|
"encryption_config_saved": "Настройки шифрования сохранены",
|
||||||
"encryption_server": "Имя сервера",
|
"encryption_server": "Имя сервера",
|
||||||
"encryption_server_enter": "Введите ваше доменное имя",
|
"encryption_server_enter": "Введите ваше доменное имя",
|
||||||
"encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату.",
|
"encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату или сертификату с поддержкой поддоменов. Если это поле не задано, сервер будет принимать TLS-соединения для любого домена.",
|
||||||
"encryption_redirect": "Автоматически перенаправлять на HTTPS",
|
"encryption_redirect": "Автоматически перенаправлять на HTTPS",
|
||||||
"encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.",
|
"encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.",
|
||||||
"encryption_https": "Порт HTTPS",
|
"encryption_https": "Порт HTTPS",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Редактировать клиента",
|
"client_edit": "Редактировать клиента",
|
||||||
"client_identifier": "Идентификатор",
|
"client_identifier": "Идентификатор",
|
||||||
"ip_address": "IP-адрес",
|
"ip_address": "IP-адрес",
|
||||||
"client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу. Обратите внимание, что использование MAC как идентификатора возможно, только если AdGuard Home также является и <0>DHCP-сервером</0>",
|
"client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу или специальному ID (можно использовать для DoT/DoH/DoQ). <0>Здесь</0> вы можете узнать больше об идентификации клиентов.",
|
||||||
"form_enter_ip": "Введите IP",
|
"form_enter_ip": "Введите IP",
|
||||||
"form_enter_mac": "Введите MAC",
|
"form_enter_mac": "Введите MAC",
|
||||||
"form_enter_id": "Введите идентификатор",
|
"form_enter_id": "Введите идентификатор",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Вы можете найти еще варианты <0>тут</0> и <1>тут</1>.",
|
"setup_dns_privacy_other_5": "Вы можете найти еще варианты <0>тут</0> и <1>тут</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Конфигурация для iOS и macOS",
|
||||||
"setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS</1> или <1>DNS-over-TLS</1>, вам нужно <0>настроить шифрование</0> в настройках AdGuard Home.",
|
"setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS</1> или <1>DNS-over-TLS</1>, вам нужно <0>настроить шифрование</0> в настройках AdGuard Home.",
|
||||||
"rewrite_added": "Правило перенаправления DNS для \"{{key}}\" успешно добавлено",
|
"rewrite_added": "Правило перенаправления DNS для \"{{key}}\" успешно добавлено",
|
||||||
"rewrite_deleted": "Правило перенаправления DNS для \"{{key}}\" успешно удалено",
|
"rewrite_deleted": "Правило перенаправления DNS для \"{{key}}\" успешно удалено",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP-адреса: {{ip}}",
|
"check_ip": "IP-адреса: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Причина: {{reason}}",
|
"check_reason": "Причина: {{reason}}",
|
||||||
"check_rule": "Правило: {{rule}}",
|
|
||||||
"check_service": "Название сервиса: {{service}}",
|
"check_service": "Название сервиса: {{service}}",
|
||||||
"service_name": "Имя сервиса",
|
"service_name": "Имя сервиса",
|
||||||
"check_not_found": "Не найдено в вашем списке фильтров",
|
"check_not_found": "Не найдено в вашем списке фильтров",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"dns_query": "ව.නා.ප. (DNS) විමසුම්",
|
"dns_query": "ව.නා.ප. (DNS) විමසුම්",
|
||||||
"blocked_by": "<0>පෙරහන් මගින් අවහිර කරන ලද</0>",
|
"blocked_by": "<0>පෙරහන් මගින් අවහිර කරන ලද</0>",
|
||||||
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
|
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
|
||||||
"stats_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
|
"stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
|
||||||
"stats_query_domain": "ජනප්රිය විමසන ලද වසම්",
|
"stats_query_domain": "ජනප්රිය විමසන ලද වසම්",
|
||||||
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
|
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
|
||||||
"for_last_days": "පසුගිය දින {{count}} සඳහා",
|
"for_last_days": "පසුගිය දින {{count}} සඳහා",
|
||||||
@@ -83,22 +83,22 @@
|
|||||||
"top_clients": "ජනප්රිය අනුග්රාහකයන්",
|
"top_clients": "ජනප්රිය අනුග්රාහකයන්",
|
||||||
"general_statistics": "පොදු සංඛ්යාලේඛන",
|
"general_statistics": "පොදු සංඛ්යාලේඛන",
|
||||||
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard browsing security ඒකකය මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ ඒකකය මගින් අවහිර කරන ලද ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි ගණන",
|
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන",
|
||||||
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන ලද",
|
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන ලද",
|
||||||
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
||||||
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
||||||
"block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
|
"block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
|
||||||
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
||||||
"use_adguard_browsing_sec": "AdGuard browsing security වෙබ් සේවාව භාවිතා කරන්න",
|
"use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ වියමන සේවාව භාවිතා කරන්න",
|
||||||
"use_adguard_parental": "AdGuard parental control වෙබ් සේවාව භාවිතා කරන්න",
|
"use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න",
|
||||||
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය බ්රව්සින් සෙකියුරිටි වෙබ් සේවාව මෙන් රහස්යතා හිතකාමී යෙ.ක්ර. අ.මු. (API) භාවිතා කරයි.",
|
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්ෂණ වියමන සේවාව මෙන් රහස්යතා හිතකාමී යෙ.ක්ර. අ.මු. (API) භාවිතා කරයි.",
|
||||||
"enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න",
|
"enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න",
|
||||||
"enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් හට පහත සෙවුම් යන්ත්ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.",
|
"enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් හට පහත සෙවුම් යන්ත්ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.",
|
||||||
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
|
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
|
||||||
"general_settings": "පොදු සැකසුම්",
|
"general_settings": "පොදු සැකසුම්",
|
||||||
"dns_settings": "DNS සැකසුම්",
|
"dns_settings": "ව.නා.ප. සැකසුම්",
|
||||||
"dns_blocklists": "ව.නා.ප. අවහිර කිරීමේ ලැයිස්තු",
|
"dns_blocklists": "ව.නා.ප. අවහිර කිරීමේ ලැයිස්තු",
|
||||||
"dns_allowlists": "ව.නා.ප. අවසර දීමේ ලැයිස්තු",
|
"dns_allowlists": "ව.නා.ප. අවසර දීමේ ලැයිස්තු",
|
||||||
"dns_blocklists_desc": "ඇඩ්ගාර්ඩ් හෝම් විසින් අවහිර කිරීමේ ලැයිස්තු වලට ගැලපෙන වසම් අවහිර කරනු ඇත.",
|
"dns_blocklists_desc": "ඇඩ්ගාර්ඩ් හෝම් විසින් අවහිර කිරීමේ ලැයිස්තු වලට ගැලපෙන වසම් අවහිර කරනු ඇත.",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත",
|
"enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත",
|
||||||
"enabled_table_header": "සබල කර ඇත",
|
"enabled_table_header": "සබල කර ඇත",
|
||||||
"name_table_header": "නම",
|
"name_table_header": "නම",
|
||||||
"list_url_table_header": "URL ලැයිස්තුව",
|
"list_url_table_header": "ඒ.ස.නි.(URL) ලැයිස්තුව",
|
||||||
"rules_count_table_header": "නීති ගණන",
|
"rules_count_table_header": "නීති ගණන",
|
||||||
"last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද",
|
"last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද",
|
||||||
"actions_table_header": "ක්රියාමාර්ග",
|
"actions_table_header": "ක්රියාමාර්ග",
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න",
|
"add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න",
|
||||||
"cancel_btn": "අහෝසි කරන්න",
|
"cancel_btn": "අහෝසි කරන්න",
|
||||||
"enter_name_hint": "නම ඇතුළත් කරන්න",
|
"enter_name_hint": "නම ඇතුළත් කරන්න",
|
||||||
"enter_url_or_path_hint": "ලැයිස්තුවක URL හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
|
"enter_url_or_path_hint": "ලැයිස්තුවක ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
|
||||||
"check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න",
|
"check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න",
|
||||||
"new_blocklist": "නව අවහිර කිරීමේ ලැයිස්තුව",
|
"new_blocklist": "නව අවහිර කිරීමේ ලැයිස්තුව",
|
||||||
"new_allowlist": "නව අවසර දීමේ ලැයිස්තුව",
|
"new_allowlist": "නව අවසර දීමේ ලැයිස්තුව",
|
||||||
@@ -142,10 +142,10 @@
|
|||||||
"edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න",
|
"edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න",
|
||||||
"choose_blocklist": "අවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
"choose_blocklist": "අවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
||||||
"choose_allowlist": "අනවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
"choose_allowlist": "අනවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
||||||
"enter_valid_blocklist": "අවහිර කිරීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
"enter_valid_blocklist": "අවහිර කිරීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.",
|
||||||
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.",
|
||||||
"form_error_url_format": "වලංගු නොවන 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": "අභිරුචි පෙරීමේ නීති",
|
||||||
"custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුළත් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.",
|
"custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුළත් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.",
|
||||||
"examples_title": "උදාහරණ",
|
"examples_title": "උදාහරණ",
|
||||||
@@ -156,11 +156,11 @@
|
|||||||
"example_comment_meaning": "විස්තර කිරීමක්",
|
"example_comment_meaning": "විස්තර කිරීමක්",
|
||||||
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්",
|
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්",
|
||||||
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්ය වාක්යවිධියට ගැලපෙන වසම් වෙත පිවිසීම අවහිර කරයි",
|
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්ය වාක්යවිධියට ගැලපෙන වසම් වෙත පිවිසීම අවහිර කරයි",
|
||||||
"example_upstream_regular": "සාමාන්ය DNS (UDP හරහා)",
|
"example_upstream_regular": "සාමාන්ය ව.නා.ප. (UDP හරහා)",
|
||||||
"example_upstream_dot": "සංකේතාංකනය කළ <0>DNS-over-TLS</0>",
|
"example_upstream_dot": "සංකේතාංකනය කළ <0>DNS-over-TLS</0>",
|
||||||
"example_upstream_doh": "සංකේතාංකනය කළ <0>DNS-over-HTTPS</0>",
|
"example_upstream_doh": "සංකේතාංකනය කළ <0>DNS-over-HTTPS</0>",
|
||||||
"example_upstream_doq": "සංකේතාංකනය කළ <0>DNS-over-QUIC</0>",
|
"example_upstream_doq": "සංකේතාංකනය කළ <0>DNS-over-QUIC</0>",
|
||||||
"example_upstream_tcp": "සාමාන්ය DNS (TCP හරහා)",
|
"example_upstream_tcp": "සාමාන්ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා) ",
|
||||||
"all_lists_up_to_date_toast": "සියලුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
|
"all_lists_up_to_date_toast": "සියලුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
|
||||||
"dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායකයන් නිවැරදිව ක්රියා කරයි",
|
"dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායකයන් නිවැරදිව ක්රියා කරයි",
|
||||||
"dns_test_not_ok_toast": "සේවාදායක \"{{key}}\": භාවිතා කළ නොහැකි විය, කරුණාකර ඔබ එය නිවැරදිව ලියා ඇත්දැයි පරීක්ෂා කරන්න",
|
"dns_test_not_ok_toast": "සේවාදායක \"{{key}}\": භාවිතා කළ නොහැකි විය, කරුණාකර ඔබ එය නිවැරදිව ලියා ඇත්දැයි පරීක්ෂා කරන්න",
|
||||||
@@ -227,22 +227,21 @@
|
|||||||
"source_label": "මූලාශ්රය",
|
"source_label": "මූලාශ්රය",
|
||||||
"found_in_known_domain_db": "දැනුවත් වසම් දත්ත ගබඩාවේ හමු විය.",
|
"found_in_known_domain_db": "දැනුවත් වසම් දත්ත ගබඩාවේ හමු විය.",
|
||||||
"category_label": "ප්රවර්ගය",
|
"category_label": "ප්රවර්ගය",
|
||||||
"rule_label": "නීතිය",
|
|
||||||
"list_label": "ලැයිස්තුව",
|
"list_label": "ලැයිස්තුව",
|
||||||
"unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි",
|
"unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි",
|
||||||
"known_tracker": "දැනුවත් ලුහුබැඳීමක්",
|
"known_tracker": "දැනුවත් ලුහුබැඳීමක්",
|
||||||
"install_welcome_title": "ඇඩ්ගාර්ඩ් හෝම් වෙත සාදරයෙන් පිළිගනිමු!",
|
"install_welcome_title": "ඇඩ්ගාර්ඩ් හෝම් වෙත සාදරයෙන් පිළිගනිමු!",
|
||||||
"install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකි. ඔබගේ මුළු ජාලය සහ සියලුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්ය නොවේ.",
|
"install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකි. ඔබගේ මුළු ජාලය සහ සියලුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්ය නොවේ.",
|
||||||
"install_settings_title": "පරිපාලක වෙබ් අතුරු මුහුණත",
|
"install_settings_title": "පරිපාලක වියමන අතුරු මුහුණත",
|
||||||
"install_settings_listen": "සවන් දෙන අතුරු මුහුණත",
|
"install_settings_listen": "සවන් දෙන අතුරු මුහුණත",
|
||||||
"install_settings_port": "කවුළුව",
|
"install_settings_port": "කවුළුව",
|
||||||
"install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණත පහත ලිපිනයන්ගෙන් ප්රවේශ විය හැකිය:",
|
"install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණත පහත ලිපිනයන්ගෙන් ප්රවේශ විය හැකිය:",
|
||||||
"form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න",
|
"form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න",
|
||||||
"install_settings_dns": "ව.නා.ප. සේවාදායකය",
|
"install_settings_dns": "ව.නා.ප. සේවාදායකය",
|
||||||
"install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්යාසගත කිරීමට අවශ්ය වනු ඇත:",
|
"install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්යාසගත කිරීමට අවශ්ය වනු ඇත:",
|
||||||
"install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්",
|
"install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්",
|
||||||
"install_auth_title": "සත්යාපනය",
|
"install_auth_title": "සත්යාපනය",
|
||||||
"install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වෙබ් අතුරු මුහුණතට මුරපද සත්යාපනය වින්යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාලයෙන් පමණක් ප්රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.",
|
"install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණතට මුරපද සත්යාපනය වින්යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාලයෙන් පමණක් ප්රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.",
|
||||||
"install_auth_username": "පරිශීලක නාමය",
|
"install_auth_username": "පරිශීලක නාමය",
|
||||||
"install_auth_password": "මුරපදය",
|
"install_auth_password": "මුරපදය",
|
||||||
"install_auth_confirm": "මුරපදය තහවුරු කරන්න",
|
"install_auth_confirm": "මුරපදය තහවුරු කරන්න",
|
||||||
@@ -284,7 +283,7 @@
|
|||||||
"open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න",
|
"open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න",
|
||||||
"install_saved": "සාර්ථකව සුරකින ලදි",
|
"install_saved": "සාර්ථකව සුරකින ලදි",
|
||||||
"encryption_title": "සංකේතාංකනය",
|
"encryption_title": "සංකේතාංකනය",
|
||||||
"encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වෙබ් අතුරු මුහුණත සහය දක්වයි",
|
"encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි",
|
||||||
"encryption_config_saved": "සංකේතාංකන වින්යාසය සුරකින ලදි",
|
"encryption_config_saved": "සංකේතාංකන වින්යාසය සුරකින ලදි",
|
||||||
"encryption_server": "සේවාදායකයේ නම",
|
"encryption_server": "සේවාදායකයේ නම",
|
||||||
"encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුළත් කරන්න",
|
"encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුළත් කරන්න",
|
||||||
@@ -340,7 +339,6 @@
|
|||||||
"client_edit": "අනුග්රාහකය සංස්කරණය කරන්න",
|
"client_edit": "අනුග්රාහකය සංස්කරණය කරන්න",
|
||||||
"client_identifier": "හඳුන්වනය",
|
"client_identifier": "හඳුන්වනය",
|
||||||
"ip_address": "අ.ජා. කෙ. (IP) ලිපිනය",
|
"ip_address": "අ.ජා. කෙ. (IP) ලිපිනය",
|
||||||
"client_identifier_desc": "අනුග්රාහකයන් අ.ජා. කෙ. (IP) ලිපිනයක් හෝ මා.ප්ර.පා. (MAC) ලිපිනයක් මගින් හඳුනාගත හැකිය. මා.ප්ර.පා. හඳුන්වනයක් ලෙස භාවිතා කළ හැක්කේ ඇඩ්ගාර්ඩ් හෝම් ද <0>DHCP සේවාදායකයක්</0> නම් පමණක් බව කරුණාවෙන් සලකන්න. ",
|
|
||||||
"form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න",
|
"form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න",
|
||||||
"form_enter_mac": "මා.ප්ර.පා. (MAC) ඇතුළත් කරන්න",
|
"form_enter_mac": "මා.ප්ර.පා. (MAC) ඇතුළත් කරන්න",
|
||||||
"form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න",
|
"form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න",
|
||||||
@@ -462,7 +460,6 @@
|
|||||||
"check_ip": "අ.ජා. කෙ. (IP) ලිපින: {{ip}}",
|
"check_ip": "අ.ජා. කෙ. (IP) ලිපින: {{ip}}",
|
||||||
"check_cname": "අන්. නාමය (CNAME): {{cname}}",
|
"check_cname": "අන්. නාමය (CNAME): {{cname}}",
|
||||||
"check_reason": "හේතුව: {{reason}}",
|
"check_reason": "හේතුව: {{reason}}",
|
||||||
"check_rule": "නීතිය: {{rule}}",
|
|
||||||
"check_service": "සේවාවෙහි නම: {{service}}",
|
"check_service": "සේවාවෙහි නම: {{service}}",
|
||||||
"service_name": "සේවාවේ නම",
|
"service_name": "සේවාවේ නම",
|
||||||
"check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක",
|
"check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක",
|
||||||
@@ -483,7 +480,7 @@
|
|||||||
"show_blocked_responses": "අවහිර කර ඇත",
|
"show_blocked_responses": "අවහිර කර ඇත",
|
||||||
"show_whitelisted_responses": "සුදු ලැයිස්තුගත කර ඇත",
|
"show_whitelisted_responses": "සුදු ලැයිස්තුගත කර ඇත",
|
||||||
"blocked_safebrowsing": "ආරක්ෂිත සෙවීම මගින් අවහිර කරන ලද",
|
"blocked_safebrowsing": "ආරක්ෂිත සෙවීම මගින් අවහිර කරන ලද",
|
||||||
"blocked_adult_websites": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
|
"blocked_adult_websites": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
|
||||||
"blocked_threats": "අවහිර කළ තර්ජන",
|
"blocked_threats": "අවහිර කළ තර්ජන",
|
||||||
"allowed": "අවසර ලත්",
|
"allowed": "අවසර ලත්",
|
||||||
"filtered": "පෙරහන් කරන ලද",
|
"filtered": "පෙරහන් කරන ලද",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Nesprávny formát IPv4",
|
"form_error_ip_format": "Nesprávny formát IPv4",
|
||||||
"form_error_mac_format": "Nesprávny MAC formát",
|
"form_error_mac_format": "Nesprávny MAC formát",
|
||||||
"form_error_client_id_format": "Neplatný formát client ID",
|
"form_error_client_id_format": "Neplatný formát client ID",
|
||||||
|
"form_error_server_name": "Neplatné meno servera",
|
||||||
"form_error_positive": "Musí byť väčšie ako 0",
|
"form_error_positive": "Musí byť väčšie ako 0",
|
||||||
"form_error_negative": "Musí byť číslo 0 alebo viac",
|
"form_error_negative": "Musí byť číslo 0 alebo viac",
|
||||||
"range_end_error": "Musí byť väčšie ako začiatok rozsahu",
|
"range_end_error": "Musí byť väčšie ako začiatok rozsahu",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Vlastná IP adresa",
|
"custom_ip": "Vlastná IP adresa",
|
||||||
"blocking_ipv4": "Blokovanie IPv4",
|
"blocking_ipv4": "Blokovanie IPv4",
|
||||||
"blocking_ipv6": "Blokovanie IPv6",
|
"blocking_ipv6": "Blokovanie IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"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. <a>Tu</a> sa dozviete viac o tom, ako identifikovať klientov.",
|
||||||
"download_mobileconfig_doh": "Prevziať .mobileconfig pre DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Prevziať .mobileconfig pre DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Prevziať .mobileconfig pre DNS-over-TLS",
|
"download_mobileconfig_dot": "Prevziať .mobileconfig pre DNS-over-TLS",
|
||||||
|
"download_mobileconfig": "Stiahnuť konfiguračný súbor",
|
||||||
"plain_dns": "Obyčajné DNS",
|
"plain_dns": "Obyčajné DNS",
|
||||||
"form_enter_rate_limit": "Zadajte rýchlostný limit",
|
"form_enter_rate_limit": "Zadajte rýchlostný limit",
|
||||||
"rate_limit": "Rýchlostný limit",
|
"rate_limit": "Rýchlostný limit",
|
||||||
@@ -269,7 +276,6 @@
|
|||||||
"source_label": "Zdroj",
|
"source_label": "Zdroj",
|
||||||
"found_in_known_domain_db": "Nájdené v databáze známych domén.",
|
"found_in_known_domain_db": "Nájdené v databáze známych domén.",
|
||||||
"category_label": "Kategória",
|
"category_label": "Kategória",
|
||||||
"rule_label": "Pravidlo",
|
|
||||||
"list_label": "Zoznam",
|
"list_label": "Zoznam",
|
||||||
"unknown_filter": "Neznámy filter {{filterId}}",
|
"unknown_filter": "Neznámy filter {{filterId}}",
|
||||||
"known_tracker": "Známy sledovač",
|
"known_tracker": "Známy sledovač",
|
||||||
@@ -330,7 +336,6 @@
|
|||||||
"encryption_config_saved": "Konfigurácia šifrovania uložená",
|
"encryption_config_saved": "Konfigurácia šifrovania uložená",
|
||||||
"encryption_server": "Meno servera",
|
"encryption_server": "Meno servera",
|
||||||
"encryption_server_enter": "Zadajte meno Vašej domény",
|
"encryption_server_enter": "Zadajte meno Vašej domény",
|
||||||
"encryption_server_desc": "Ak chcete používať HTTPS, musíte zadať meno servera, ktoré zodpovedá Vášmu SSL certifikátu.",
|
|
||||||
"encryption_redirect": "Automaticky presmerovať na HTTPS",
|
"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_redirect_desc": "Ak je táto možnosť začiarknutá, služba AdGuard Home Vás automaticky presmeruje z adresy HTTP na adresy HTTPS.",
|
||||||
"encryption_https": "HTTPS port",
|
"encryption_https": "HTTPS port",
|
||||||
@@ -386,7 +391,6 @@
|
|||||||
"client_edit": "Upraviť klienta",
|
"client_edit": "Upraviť klienta",
|
||||||
"client_identifier": "Identifikátor",
|
"client_identifier": "Identifikátor",
|
||||||
"ip_address": "IP adresa",
|
"ip_address": "IP adresa",
|
||||||
"client_identifier_desc": "Klienti môžu byť identifikovaní podľa IP adresy, CIDR alebo MAC adresy. Upozorňujeme, že používanie MAC ako identifikátora je možné len vtedy, ak je AdGuard Home tiež <0>DHCP server</0>",
|
|
||||||
"form_enter_ip": "Zadajte IP adresu",
|
"form_enter_ip": "Zadajte IP adresu",
|
||||||
"form_enter_mac": "Zadajte MAC adresu",
|
"form_enter_mac": "Zadajte MAC adresu",
|
||||||
"form_enter_id": "Zadajte identifikátor",
|
"form_enter_id": "Zadajte identifikátor",
|
||||||
@@ -430,6 +434,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podporuje <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podporuje <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podporuje <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podporuje <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Viac implementácií nájdete <0>tu</0> a <1>tu</1>.",
|
"setup_dns_privacy_other_5": "Viac implementácií nájdete <0>tu</0> a <1>tu</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Konfigurácia iOS a macOS",
|
||||||
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>.",
|
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>.",
|
||||||
"rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané",
|
"rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané",
|
||||||
"rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané",
|
"rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané",
|
||||||
@@ -529,7 +534,6 @@
|
|||||||
"check_ip": "IP adresy: {{ip}}",
|
"check_ip": "IP adresy: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Dôvod: {{reason}}",
|
"check_reason": "Dôvod: {{reason}}",
|
||||||
"check_rule": "Pravidlo: {{rule}}",
|
|
||||||
"check_service": "Meno služby: {{service}}",
|
"check_service": "Meno služby: {{service}}",
|
||||||
"service_name": "Názov služby",
|
"service_name": "Názov služby",
|
||||||
"check_not_found": "Nenašlo sa vo Vašom zozname filtrov",
|
"check_not_found": "Nenašlo sa vo Vašom zozname filtrov",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Neveljaven format IP",
|
"form_error_ip_format": "Neveljaven format IP",
|
||||||
"form_error_mac_format": "Neveljaven MAC format",
|
"form_error_mac_format": "Neveljaven MAC format",
|
||||||
"form_error_client_id_format": "Neveljaven format ID odjemalca",
|
"form_error_client_id_format": "Neveljaven format ID odjemalca",
|
||||||
|
"form_error_server_name": "Neveljavno ime strežnika",
|
||||||
"form_error_positive": "Mora biti večja od 0",
|
"form_error_positive": "Mora biti večja od 0",
|
||||||
"form_error_negative": "Mora biti enako ali več kot 0",
|
"form_error_negative": "Mora biti enako ali več kot 0",
|
||||||
"range_end_error": "Mora biti večji od začtka razpona",
|
"range_end_error": "Mora biti večji od začtka razpona",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "IP po meri",
|
"custom_ip": "IP po meri",
|
||||||
"blocking_ipv4": "Onemogočanje IPv4",
|
"blocking_ipv4": "Onemogočanje IPv4",
|
||||||
"blocking_ipv6": "Onemogočanje IPv6",
|
"blocking_ipv6": "Onemogočanje IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-prek-HTTPS",
|
"dns_over_https": "DNS-prek-HTTPS",
|
||||||
"dns_over_tls": "DNS-prek-TLS",
|
"dns_over_tls": "DNS-prek-TLS",
|
||||||
|
"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. <a>Tukaj</a> lahko izveste več o prepoznavanju odjemalcev.",
|
||||||
"download_mobileconfig_doh": "Prenos .mobileconfig za DNS-preko-HTTPS",
|
"download_mobileconfig_doh": "Prenos .mobileconfig za DNS-preko-HTTPS",
|
||||||
"download_mobileconfig_dot": "Prenos .mobileconfig za DNS-preko-TLS",
|
"download_mobileconfig_dot": "Prenos .mobileconfig za DNS-preko-TLS",
|
||||||
|
"download_mobileconfig": "Prenesi nastavitveno datoteko",
|
||||||
"plain_dns": "Navadni DNS",
|
"plain_dns": "Navadni DNS",
|
||||||
"form_enter_rate_limit": "Vnesite omejitev hitrosti",
|
"form_enter_rate_limit": "Vnesite omejitev hitrosti",
|
||||||
"rate_limit": "Omejitev hitrosti",
|
"rate_limit": "Omejitev hitrosti",
|
||||||
@@ -269,7 +276,7 @@
|
|||||||
"source_label": "Vir",
|
"source_label": "Vir",
|
||||||
"found_in_known_domain_db": "Najdeno v zbirki podatkov znanih domen.",
|
"found_in_known_domain_db": "Najdeno v zbirki podatkov znanih domen.",
|
||||||
"category_label": "Kategorija",
|
"category_label": "Kategorija",
|
||||||
"rule_label": "Pravilo",
|
"rule_label": "Pravila",
|
||||||
"list_label": "Seznam",
|
"list_label": "Seznam",
|
||||||
"unknown_filter": "Neznan filter {{filterId}}",
|
"unknown_filter": "Neznan filter {{filterId}}",
|
||||||
"known_tracker": "Znan sledilec",
|
"known_tracker": "Znan sledilec",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "Uredi odjemalca",
|
"client_edit": "Uredi odjemalca",
|
||||||
"client_identifier": "Identifikator",
|
"client_identifier": "Identifikator",
|
||||||
"ip_address": "IP naslov",
|
"ip_address": "IP naslov",
|
||||||
"client_identifier_desc": "Odjemalce je mogoče identificirati po naslovu IP, CIDR, MAC naslovu. Upoštevajte, da je uporaba MAC kot identifikatorja mogoča le, če je AdGuard Home tudi <0>strežnik DHCP</0>",
|
"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</0> lahko izveste več o prepoznavanju odjemalcev.",
|
||||||
"form_enter_ip": "Vnesite IP",
|
"form_enter_ip": "Vnesite IP",
|
||||||
"form_enter_mac": "Vnesite MAC",
|
"form_enter_mac": "Vnesite MAC",
|
||||||
"form_enter_id": "Vnesi identifikatorja",
|
"form_enter_id": "Vnesi identifikatorja",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podpira <1>DNS-prek-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podpira <1>DNS-prek-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podpira <1>DNS-prek-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podpira <1>DNS-prek-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Našli boste več izvedb <0>tukaj</0> in <1>tukaj</1>.",
|
"setup_dns_privacy_other_5": "Našli boste več izvedb <0>tukaj</0> in <1>tukaj</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Nastavitve iOS in macOS",
|
||||||
"setup_dns_notice": "Za uporabo <1>DNS-prek-HTTPS</1> ali <1>DNS-prek-TLS</1>, morate <0>konfigurirati šifriranje</0> v nastavitvah AdGuard Home.",
|
"setup_dns_notice": "Za uporabo <1>DNS-prek-HTTPS</1> ali <1>DNS-prek-TLS</1>, morate <0>konfigurirati šifriranje</0> v nastavitvah AdGuard Home.",
|
||||||
"rewrite_added": "Uspešno je dodano DNS prepisovanje za \"{{key}}\"",
|
"rewrite_added": "Uspešno je dodano DNS prepisovanje za \"{{key}}\"",
|
||||||
"rewrite_deleted": "Uspešno je izbrisano DNS prepisovanje za \"{{key}}\"",
|
"rewrite_deleted": "Uspešno je izbrisano DNS prepisovanje za \"{{key}}\"",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP naslovi: {{ip}}",
|
"check_ip": "IP naslovi: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Razlog: {{reason}}",
|
"check_reason": "Razlog: {{reason}}",
|
||||||
"check_rule": "Pravilo: {{rule}}",
|
|
||||||
"check_service": "Ime storitve: {{service}}",
|
"check_service": "Ime storitve: {{service}}",
|
||||||
"service_name": "Ime storitve",
|
"service_name": "Ime storitve",
|
||||||
"check_not_found": "Ni najdeno na vašem seznamu filtrov",
|
"check_not_found": "Ni najdeno na vašem seznamu filtrov",
|
||||||
|
|||||||
@@ -269,7 +269,6 @@
|
|||||||
"source_label": "Izvor",
|
"source_label": "Izvor",
|
||||||
"found_in_known_domain_db": "Pronađeno u poznatim bazama podataka domena.",
|
"found_in_known_domain_db": "Pronađeno u poznatim bazama podataka domena.",
|
||||||
"category_label": "Kategorija",
|
"category_label": "Kategorija",
|
||||||
"rule_label": "Pravilo",
|
|
||||||
"list_label": "Lista",
|
"list_label": "Lista",
|
||||||
"unknown_filter": "Nepoznat filter {{filterId}}",
|
"unknown_filter": "Nepoznat filter {{filterId}}",
|
||||||
"known_tracker": "Poznato praćenje",
|
"known_tracker": "Poznato praćenje",
|
||||||
@@ -330,7 +329,6 @@
|
|||||||
"encryption_config_saved": "Konfiguracija šifrovanja je sačuvana",
|
"encryption_config_saved": "Konfiguracija šifrovanja je sačuvana",
|
||||||
"encryption_server": "Ime servera",
|
"encryption_server": "Ime servera",
|
||||||
"encryption_server_enter": "Unesite vaše ime domena",
|
"encryption_server_enter": "Unesite vaše ime domena",
|
||||||
"encryption_server_desc": "Kako biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL sertifikatom.",
|
|
||||||
"encryption_redirect": "Automatski preusmeri na HTTPS",
|
"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_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.",
|
||||||
"encryption_https": "HTTPS port",
|
"encryption_https": "HTTPS port",
|
||||||
@@ -386,7 +384,6 @@
|
|||||||
"client_edit": "Izmeni klijent",
|
"client_edit": "Izmeni klijent",
|
||||||
"client_identifier": "Identifikator",
|
"client_identifier": "Identifikator",
|
||||||
"ip_address": "IP adresa",
|
"ip_address": "IP adresa",
|
||||||
"client_identifier_desc": "Klijenti mogu da budu prepoznati po IP adresi ili MAC adresi. Imajte na umu da je korišćenje MAC adrese kao identifikatora moguće samo ako je AdGuard Home takođe a <0>DHCP server</0>",
|
|
||||||
"form_enter_ip": "Unesite IP",
|
"form_enter_ip": "Unesite IP",
|
||||||
"form_enter_mac": "Unesite MAC",
|
"form_enter_mac": "Unesite MAC",
|
||||||
"form_enter_id": "Unesite identifikator",
|
"form_enter_id": "Unesite identifikator",
|
||||||
@@ -529,7 +526,6 @@
|
|||||||
"check_ip": "IP adrese: {{ip}}",
|
"check_ip": "IP adrese: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Razlog: {{reason}}",
|
"check_reason": "Razlog: {{reason}}",
|
||||||
"check_rule": "Pravilo: {{rule}}",
|
|
||||||
"check_service": "Ime usluge: {{service}}",
|
"check_service": "Ime usluge: {{service}}",
|
||||||
"service_name": "Ime usluge",
|
"service_name": "Ime usluge",
|
||||||
"check_not_found": "Nije pronađeno na vašoj listi filtera",
|
"check_not_found": "Nije pronađeno na vašoj listi filtera",
|
||||||
|
|||||||
@@ -177,7 +177,6 @@
|
|||||||
"source_label": "Källa",
|
"source_label": "Källa",
|
||||||
"found_in_known_domain_db": "Hittad i domändatabas.",
|
"found_in_known_domain_db": "Hittad i domändatabas.",
|
||||||
"category_label": "Kategori",
|
"category_label": "Kategori",
|
||||||
"rule_label": "Regel",
|
|
||||||
"unknown_filter": "Okänt filter {{filterId}}",
|
"unknown_filter": "Okänt filter {{filterId}}",
|
||||||
"install_welcome_title": "Välkommen till AdGuard Home!",
|
"install_welcome_title": "Välkommen till AdGuard Home!",
|
||||||
"install_welcome_desc": "AdGuard Home är en DNS-server för nätverkstäckande annons- och spårningsblockering. Dess syfte är att de dig kontroll över hela nätverket och alla dina enheter, utan behov av att använda klientbaserade program.",
|
"install_welcome_desc": "AdGuard Home är en DNS-server för nätverkstäckande annons- och spårningsblockering. Dess syfte är att de dig kontroll över hela nätverket och alla dina enheter, utan behov av att använda klientbaserade program.",
|
||||||
@@ -235,7 +234,6 @@
|
|||||||
"encryption_config_saved": "Krypteringsinställningar sparade",
|
"encryption_config_saved": "Krypteringsinställningar sparade",
|
||||||
"encryption_server": "Servernamn",
|
"encryption_server": "Servernamn",
|
||||||
"encryption_server_enter": "Skriv in ditt domännamn",
|
"encryption_server_enter": "Skriv in ditt domännamn",
|
||||||
"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": "Omdirigera till HTTPS automatiskt",
|
||||||
"encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.",
|
"encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.",
|
||||||
"encryption_https": "HTTPS-port",
|
"encryption_https": "HTTPS-port",
|
||||||
@@ -287,7 +285,6 @@
|
|||||||
"client_edit": "Redigera klient",
|
"client_edit": "Redigera klient",
|
||||||
"client_identifier": "Identifikator",
|
"client_identifier": "Identifikator",
|
||||||
"ip_address": "IP-adress",
|
"ip_address": "IP-adress",
|
||||||
"client_identifier_desc": "Klienter kan identifieras genom IP-adresser eller MAC-adresser. Notera att användning av MAC som identifierare bara är möjligt om AdGuard Home också är en <0>DHCP-server</0>",
|
|
||||||
"form_enter_ip": "Skriv in IP",
|
"form_enter_ip": "Skriv in IP",
|
||||||
"form_enter_mac": "Skriv in MAC",
|
"form_enter_mac": "Skriv in MAC",
|
||||||
"form_client_name": "Skriv in klientnamn",
|
"form_client_name": "Skriv in klientnamn",
|
||||||
|
|||||||
@@ -200,7 +200,6 @@
|
|||||||
"source_label": "ที่มา",
|
"source_label": "ที่มา",
|
||||||
"found_in_known_domain_db": "พบในฐานข้อมูลโดเมนที่รู้จัก",
|
"found_in_known_domain_db": "พบในฐานข้อมูลโดเมนที่รู้จัก",
|
||||||
"category_label": "ประเภท",
|
"category_label": "ประเภท",
|
||||||
"rule_label": "กฎ",
|
|
||||||
"unknown_filter": "ตัวกรองที่ไม่รู้จัก {{filterId}}",
|
"unknown_filter": "ตัวกรองที่ไม่รู้จัก {{filterId}}",
|
||||||
"install_welcome_title": "ยินดีต้อนรับสู่ AdGuard Home",
|
"install_welcome_title": "ยินดีต้อนรับสู่ AdGuard Home",
|
||||||
"install_welcome_desc": "AdGuard Home เป็นเซิร์ฟเวอร์ DNS ปิดกั้นโฆษณาและติดตามทั่วทั้งเครือข่าย วัตถุประสงค์คือเพื่อให้คุณควบคุมเครือข่ายทั้งหมดและอุปกรณ์ทั้งหมดของคุณและไม่จำเป็นต้องใช้โปรแกรมฝั่งไคลเอ็นต์",
|
"install_welcome_desc": "AdGuard Home เป็นเซิร์ฟเวอร์ DNS ปิดกั้นโฆษณาและติดตามทั่วทั้งเครือข่าย วัตถุประสงค์คือเพื่อให้คุณควบคุมเครือข่ายทั้งหมดและอุปกรณ์ทั้งหมดของคุณและไม่จำเป็นต้องใช้โปรแกรมฝั่งไคลเอ็นต์",
|
||||||
@@ -258,7 +257,6 @@
|
|||||||
"encryption_config_saved": "บันทึกการตั้งค่าเข้ารหัสเรียบร้อยแล้ว",
|
"encryption_config_saved": "บันทึกการตั้งค่าเข้ารหัสเรียบร้อยแล้ว",
|
||||||
"encryption_server": "ชื่อเซิร์ฟเวอร์",
|
"encryption_server": "ชื่อเซิร์ฟเวอร์",
|
||||||
"encryption_server_enter": "ป้อนชื่อโดเมน",
|
"encryption_server_enter": "ป้อนชื่อโดเมน",
|
||||||
"encryption_server_desc": "ในการใช้ HTTPS คุณต้องป้อนชื่อเซิร์ฟเวอร์ที่ตรงกับใบรับรอง SSL ของคุณ",
|
|
||||||
"encryption_redirect": "ไปเส้นทาง HTTPS อัตโนมัติ",
|
"encryption_redirect": "ไปเส้นทาง HTTPS อัตโนมัติ",
|
||||||
"encryption_redirect_desc": "หากเลือกตัวเลือกนี้ AdGuard Home จะเปลี่ยนเส้นทางคุณจากที่อยู่ HTTP ไปยัง HTTPS โดยอัตโนมัติ",
|
"encryption_redirect_desc": "หากเลือกตัวเลือกนี้ AdGuard Home จะเปลี่ยนเส้นทางคุณจากที่อยู่ HTTP ไปยัง HTTPS โดยอัตโนมัติ",
|
||||||
"encryption_https": "พอร์ท HTTPS",
|
"encryption_https": "พอร์ท HTTPS",
|
||||||
@@ -312,7 +310,6 @@
|
|||||||
"client_edit": "แก้ไขเครื่องลูกข่าย",
|
"client_edit": "แก้ไขเครื่องลูกข่าย",
|
||||||
"client_identifier": "ตรวจสอบโดย",
|
"client_identifier": "ตรวจสอบโดย",
|
||||||
"ip_address": "IP addresses",
|
"ip_address": "IP addresses",
|
||||||
"client_identifier_desc": "ลูกค้าสามารถระบุได้โดยที่อยู่ IP, CIDR, ที่อยู่ MAC โปรดทราบว่าการใช้ MAC เป็นตัวระบุเป็นไปได้ก็ต่อเมื่อ AdGuard Home เป็น <0>เซิร์ฟเวอร์ DHCP</0> ด้วย",
|
|
||||||
"form_enter_ip": "กรอก IP",
|
"form_enter_ip": "กรอก IP",
|
||||||
"form_enter_mac": "กรอก MAC",
|
"form_enter_mac": "กรอก MAC",
|
||||||
"form_enter_id": "ป้อนตัวระบุ",
|
"form_enter_id": "ป้อนตัวระบุ",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Geçersiz IP biçimi",
|
"form_error_ip_format": "Geçersiz IP biçimi",
|
||||||
"form_error_mac_format": "Geçersiz MAC biçimi",
|
"form_error_mac_format": "Geçersiz MAC biçimi",
|
||||||
"form_error_client_id_format": "Geçersiz istemci kimliği biçimi",
|
"form_error_client_id_format": "Geçersiz istemci kimliği biçimi",
|
||||||
|
"form_error_server_name": "Geçersiz sunucu adı",
|
||||||
"form_error_positive": "0'dan büyük olmalı",
|
"form_error_positive": "0'dan büyük olmalı",
|
||||||
"form_error_negative": "0 veya daha büyük olmalıdır",
|
"form_error_negative": "0 veya daha büyük olmalıdır",
|
||||||
"range_end_error": "Başlangıç aralığından daha büyük olmalı",
|
"range_end_error": "Başlangıç aralığından daha büyük olmalı",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "Özel IP",
|
"custom_ip": "Özel IP",
|
||||||
"blocking_ipv4": "IPv4 engelleme",
|
"blocking_ipv4": "IPv4 engelleme",
|
||||||
"blocking_ipv6": "IPv6 engelleme",
|
"blocking_ipv6": "IPv6 engelleme",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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. <a>Burada</a> istemcileri nasıl belirleyeceğiniz hakkında daha fazla bilgi edinebilirsiniz.",
|
||||||
"download_mobileconfig_doh": "DNS-over-HTTPS için .mobileconfig dosyasını indir",
|
"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_dot": "DNS-over-TLS için .mobileconfig dosyasını indir",
|
||||||
|
"download_mobileconfig": "Yapılandırma dosyasını indir",
|
||||||
"plain_dns": "Sade DNS",
|
"plain_dns": "Sade DNS",
|
||||||
"form_enter_rate_limit": "Sıklık limitini girin",
|
"form_enter_rate_limit": "Sıklık limitini girin",
|
||||||
"rate_limit": "Sıklık limiti",
|
"rate_limit": "Sıklık limiti",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "Şifreleme ayarı kaydedildi",
|
"encryption_config_saved": "Şifreleme ayarı kaydedildi",
|
||||||
"encryption_server": "Sunucu adı",
|
"encryption_server": "Sunucu adı",
|
||||||
"encryption_server_enter": "Alan adınızı girin",
|
"encryption_server_enter": "Alan adınızı girin",
|
||||||
"encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla eşleşen sunucu adını girmeniz gerekir",
|
"encryption_server_desc": "HTTPS kullanmak için, SSL sertifikanız veya joker karakter sertifikanızla eşleşen sunucu adını girmeniz gerekir. Alan ayarlanmazsa, herhangi bir alan adı için TKG bağlantılarını kabul eder.",
|
||||||
"encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
|
"encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
|
||||||
"encryption_redirect_desc": "Etkinleştirirseniz AdGuard Home sizi HTTP yerine HTTPS adreslerine yönlendirir.",
|
"encryption_redirect_desc": "Etkinleştirirseniz AdGuard Home sizi HTTP yerine HTTPS adreslerine yönlendirir.",
|
||||||
"encryption_https": "HTTPS bağlantı noktası",
|
"encryption_https": "HTTPS bağlantı noktası",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "İstemciyi düzenle",
|
"client_edit": "İstemciyi düzenle",
|
||||||
"client_identifier": "Tanımlayıcı",
|
"client_identifier": "Tanımlayıcı",
|
||||||
"ip_address": "IP adresi",
|
"ip_address": "IP adresi",
|
||||||
"client_identifier_desc": "İstemciler IP adresleri veya MAC adresleri ile tanımlanabilir. Lütfen not edin, MAC adresi ile tanımlamayı kullanmak için AdGuard Home'un <0>DHCP Sunucusu</0> olması gerekir.",
|
"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). <0>Burada</0> istemcileri nasıl belirleyeceğiniz hakkında daha fazla bilgi edinebilirsiniz.",
|
||||||
"form_enter_ip": "IP Girin",
|
"form_enter_ip": "IP Girin",
|
||||||
"form_enter_mac": "MAC Girin",
|
"form_enter_mac": "MAC Girin",
|
||||||
"form_enter_id": "Tanımlayıcı girin",
|
"form_enter_id": "Tanımlayıcı girin",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0>, <1>DNS-over-HTTPS</1> destekler.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0>, <1>DNS-over-HTTPS</1> destekler.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0>, <1>DNS-over-HTTPS</1> desteklemektedir.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0>, <1>DNS-over-HTTPS</1> desteklemektedir.",
|
||||||
"setup_dns_privacy_other_5": "<0>Burada</0> ve <1>burada</1> daha fazla uygulama bulacaksınız.",
|
"setup_dns_privacy_other_5": "<0>Burada</0> ve <1>burada</1> daha fazla uygulama bulacaksınız.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS ve macOS yapılandırması",
|
||||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> veya <1>DNS-over-TLS</1> kullanmak için, AdGuard Home ayarlarında <0>Şifreleme yapılandırmasını</0> yapmanız gerekir.",
|
"setup_dns_notice": "<1>DNS-over-HTTPS</1> veya <1>DNS-over-TLS</1> kullanmak için, AdGuard Home ayarlarında <0>Şifreleme yapılandırmasını</0> yapmanız gerekir.",
|
||||||
"rewrite_added": "\"{{key}}\" için DNS yeniden yazımı başarıyla eklendi",
|
"rewrite_added": "\"{{key}}\" için DNS yeniden yazımı başarıyla eklendi",
|
||||||
"rewrite_deleted": "\"{{key}}\" için DNS yeniden yazımı başarıyla silindi",
|
"rewrite_deleted": "\"{{key}}\" için DNS yeniden yazımı başarıyla silindi",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP adresleri: {{ip}}",
|
"check_ip": "IP adresleri: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Sebep: {{reason}}",
|
"check_reason": "Sebep: {{reason}}",
|
||||||
"check_rule": "Kural: {{rule}}",
|
|
||||||
"check_service": "Hizmet adı: {{service}}",
|
"check_service": "Hizmet adı: {{service}}",
|
||||||
"service_name": "Servis adı",
|
"service_name": "Servis adı",
|
||||||
"check_not_found": "Filtre listelerinizde bulunamadı",
|
"check_not_found": "Filtre listelerinizde bulunamadı",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "Định dạng IPv4 không hợp lệ",
|
"form_error_ip_format": "Định dạng IPv4 không hợp lệ",
|
||||||
"form_error_mac_format": "Định dạng MAC 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_client_id_format": "Định dạng client ID không hợp lệ",
|
||||||
|
"form_error_server_name": "Tên máy chủ không hợp lệ",
|
||||||
"form_error_positive": "Phải lớn hơn 0",
|
"form_error_positive": "Phải lớn hơn 0",
|
||||||
"form_error_negative": "Phải lớn hơn hoặc bằng 0",
|
"form_error_negative": "Phải lớn hơn hoặc bằng 0",
|
||||||
"range_end_error": "Phải lớn hơn khoảng bắt đầu",
|
"range_end_error": "Phải lớn hơn khoảng bắt đầu",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"encryption_settings": "Cài đặt mã hóa",
|
"encryption_settings": "Cài đặt mã hóa",
|
||||||
"dhcp_settings": "Cài đặt DHCP",
|
"dhcp_settings": "Cài đặt DHCP",
|
||||||
"upstream_dns": "Máy chủ DNS tìm kiếm",
|
"upstream_dns": "Máy chủ DNS tìm kiếm",
|
||||||
|
"upstream_dns_help": "Nhập địa chỉ máy chủ một trên mỗi dòng. <a>Tìm hiểu thêm</a> về cách định cấu hình máy chủ DNS ngược dòng.",
|
||||||
"upstream_dns_configured_in_file": "Cấu hình tại {{path}}",
|
"upstream_dns_configured_in_file": "Cấu hình tại {{path}}",
|
||||||
"test_upstream_btn": "Kiểm tra",
|
"test_upstream_btn": "Kiểm tra",
|
||||||
"upstreams": "Nguồn",
|
"upstreams": "Nguồn",
|
||||||
@@ -197,6 +199,9 @@
|
|||||||
"unblock": "Bỏ chặn",
|
"unblock": "Bỏ chặn",
|
||||||
"block": "Chặn",
|
"block": "Chặn",
|
||||||
"disallow_this_client": "Không cho phép client này",
|
"disallow_this_client": "Không cho phép client này",
|
||||||
|
"allow_this_client": "Cho phép ứng dụng khách này",
|
||||||
|
"block_for_this_client_only": "Chỉ chặn ứng dụng khách này",
|
||||||
|
"unblock_for_this_client_only": "Chỉ hủy chặn ứng dụng khách này",
|
||||||
"time_table_header": "Thời gian",
|
"time_table_header": "Thời gian",
|
||||||
"date": "Ngày",
|
"date": "Ngày",
|
||||||
"domain_name_table_header": "Tên miền",
|
"domain_name_table_header": "Tên miền",
|
||||||
@@ -243,8 +248,16 @@
|
|||||||
"custom_ip": "IP tuỳ chỉnh",
|
"custom_ip": "IP tuỳ chỉnh",
|
||||||
"blocking_ipv4": "Chặn IPv4",
|
"blocking_ipv4": "Chặn IPv4",
|
||||||
"blocking_ipv6": "Chặn IPv6",
|
"blocking_ipv6": "Chặn IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"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. <a>Tại đây</a> bạn có thể tìm hiểu thêm về cách xác định khách hàng.",
|
||||||
|
"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",
|
||||||
"plain_dns": "DNS thuần",
|
"plain_dns": "DNS thuần",
|
||||||
"form_enter_rate_limit": "Nhập giới hạn yêu cầu",
|
"form_enter_rate_limit": "Nhập giới hạn yêu cầu",
|
||||||
"rate_limit": "Giới hạn yêu cầu",
|
"rate_limit": "Giới hạn yêu cầu",
|
||||||
@@ -254,6 +267,7 @@
|
|||||||
"blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặ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",
|
"blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn",
|
||||||
"blocking_mode_default": "Mặc định: Trả lời với NXDOMAIN khi bị chặn bởi quy tắc kiểu Adblock; phản hồi với địa chỉ IP được chỉ định trong quy tắc khi bị chặn bởi quy tắc / etc / hosts-style",
|
"blocking_mode_default": "Mặc định: Trả lời với NXDOMAIN khi bị chặn bởi quy tắc kiểu Adblock; phản hồi với địa chỉ IP được chỉ định trong quy tắc khi bị chặn bởi quy tắc / etc / hosts-style",
|
||||||
|
"blocking_mode_refused": "REFUSED: Trả lời bằng mã REFUSED",
|
||||||
"blocking_mode_nxdomain": "NXDOMAIN: Phản hổi với mã NXDOMAIN",
|
"blocking_mode_nxdomain": "NXDOMAIN: Phản hổi với mã NXDOMAIN",
|
||||||
"blocking_mode_null_ip": "Null IP: Trả lời bằng không địa chỉ IP (0.0.0.0 cho A; :: cho AAAA)",
|
"blocking_mode_null_ip": "Null IP: Trả lời bằng không địa chỉ IP (0.0.0.0 cho A; :: cho AAAA)",
|
||||||
"blocking_mode_custom_ip": "IP tùy chỉnh: Phản hồi với địa chỉ IP đã được tiết lập",
|
"blocking_mode_custom_ip": "IP tùy chỉnh: Phản hồi với địa chỉ IP đã được tiết lập",
|
||||||
@@ -262,7 +276,6 @@
|
|||||||
"source_label": "Nguồn",
|
"source_label": "Nguồn",
|
||||||
"found_in_known_domain_db": "Tìm thấy trong cơ sở dữ liệu tên miền",
|
"found_in_known_domain_db": "Tìm thấy trong cơ sở dữ liệu tên miền",
|
||||||
"category_label": "Thể loại",
|
"category_label": "Thể loại",
|
||||||
"rule_label": "Quy tắc",
|
|
||||||
"list_label": "Danh sách",
|
"list_label": "Danh sách",
|
||||||
"unknown_filter": "Bộ lọc không rõ {{filterId}}",
|
"unknown_filter": "Bộ lọc không rõ {{filterId}}",
|
||||||
"known_tracker": "Theo dõi đã biết",
|
"known_tracker": "Theo dõi đã biết",
|
||||||
@@ -323,13 +336,14 @@
|
|||||||
"encryption_config_saved": "Đã lưu cấu hình mã hóa",
|
"encryption_config_saved": "Đã lưu cấu hình mã hóa",
|
||||||
"encryption_server": "Tên máy chủ",
|
"encryption_server": "Tên máy chủ",
|
||||||
"encryption_server_enter": "Nhập tên miền của bạn",
|
"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.",
|
|
||||||
"encryption_redirect": "Tự động chuyển hướng đến HTTPS",
|
"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_redirect_desc": "Nếu được chọn, AdGuard Home sẽ tự động chuyển hướng bạn từ địa chỉ HTTP sang địa chỉ HTTPS.",
|
||||||
"encryption_https": "Cổng HTTPS",
|
"encryption_https": "Cổng HTTPS",
|
||||||
"encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.",
|
"encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.",
|
||||||
"encryption_dot": "Cổng DNS-over-TLS",
|
"encryption_dot": "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_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_certificates": "Chứng chỉ",
|
"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}}</0> 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_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}}</0> 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.",
|
"encryption_certificates_input": "Sao chép/dán chứng chỉ được mã hóa PEM của bạn tại đây.",
|
||||||
@@ -377,7 +391,6 @@
|
|||||||
"client_edit": "Chỉnh Sửa Máy Khách",
|
"client_edit": "Chỉnh Sửa Máy Khách",
|
||||||
"client_identifier": "Định danh",
|
"client_identifier": "Định danh",
|
||||||
"ip_address": "Địa chỉ IP",
|
"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 hoặc địa chỉ MAC. Xin lưu ý rằng chỉ có thể sử dụng MAC làm định danh nếu AdGuard Home cũng là <0>máy chủ DHCP</0>",
|
|
||||||
"form_enter_ip": "Nhập IP",
|
"form_enter_ip": "Nhập IP",
|
||||||
"form_enter_mac": "Nhập MAC",
|
"form_enter_mac": "Nhập MAC",
|
||||||
"form_enter_id": "Nhập định danh",
|
"form_enter_id": "Nhập định danh",
|
||||||
@@ -408,6 +421,7 @@
|
|||||||
"dns_privacy": "DNS Riêng Tư",
|
"dns_privacy": "DNS Riêng Tư",
|
||||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
||||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
||||||
|
"setup_dns_privacy_3": "<0>Đây là danh sách phần mềm bạn có thể sử dụng.</0>",
|
||||||
"setup_dns_privacy_4": "Trên thiết bị chạy iOS 14 hoặc macOS Big Sur bạn có thể tải tệp '.mobileconfig' đặc biệt có chứa máy chủ <highlight>DNS-over-HTTPS</highlight> hoặc <highlight>DNS-over-TLS</highlight> trong thiết lập DNS.",
|
"setup_dns_privacy_4": "Trên thiết bị chạy iOS 14 hoặc macOS Big Sur bạn có thể tải tệp '.mobileconfig' đặc biệt có chứa máy chủ <highlight>DNS-over-HTTPS</highlight> hoặc <highlight>DNS-over-TLS</highlight> trong thiết lập DNS.",
|
||||||
"setup_dns_privacy_android_1": "Android 9 hỗ trợ DNS trên TLS nguyên bản. Để định cấu hình, hãy đi tới Cài đặt → Mạng & internet → Nâng cao → DNS Riêng Tư và nhập tên miền của bạn vào đó.",
|
"setup_dns_privacy_android_1": "Android 9 hỗ trợ DNS trên TLS nguyên bản. Để định cấu hình, hãy đi tới Cài đặt → Mạng & internet → Nâng cao → DNS Riêng Tư và nhập tên miền của bạn vào đó.",
|
||||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> hỗ trợ <1>DNS-over-HTTPS</1> và <1>DNS-over-TLS</1>.",
|
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> hỗ trợ <1>DNS-over-HTTPS</1> và <1>DNS-over-TLS</1>.",
|
||||||
@@ -420,6 +434,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> hỗ trợ <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> hỗ trợ <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> hỗ trợ <1>DNS-over-HTTPS</1>.",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> hỗ trợ <1>DNS-over-HTTPS</1>.",
|
||||||
"setup_dns_privacy_other_5": "Bạn sẽ tìm thấy nhiều triển khai hơn <0>tại đây</0> và <1>tại đây</1>.",
|
"setup_dns_privacy_other_5": "Bạn sẽ tìm thấy nhiều triển khai hơn <0>tại đây</0> và <1>tại đây</1>.",
|
||||||
|
"setup_dns_privacy_ioc_mac": "Cấu hình iOS và macOS",
|
||||||
"setup_dns_notice": "Để sử dụng <1>DNS-over-HTTPS</1> hoặc <1>DNS-over-TLS</1>, bạn cần <0>định cấu hình Mã hóa</0> trong cài đặt AdGuard Home.",
|
"setup_dns_notice": "Để sử dụng <1>DNS-over-HTTPS</1> hoặc <1>DNS-over-TLS</1>, bạn cần <0>định cấu hình Mã hóa</0> trong cài đặt AdGuard Home.",
|
||||||
"rewrite_added": "DNS viết lại cho \"{{key}}\" đã thêm thành công",
|
"rewrite_added": "DNS viết lại cho \"{{key}}\" đã thêm thành công",
|
||||||
"rewrite_deleted": "DNS viết lại cho \"{{key}}\" đã xóa thành công",
|
"rewrite_deleted": "DNS viết lại cho \"{{key}}\" đã xóa thành công",
|
||||||
@@ -519,8 +534,8 @@
|
|||||||
"check_ip": "Địa chỉ IP: {{ip}}",
|
"check_ip": "Địa chỉ IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Lý do: {{reason}}",
|
"check_reason": "Lý do: {{reason}}",
|
||||||
"check_rule": "Quy tắc: {{rule}}",
|
|
||||||
"check_service": "Tên dịch vụ: {{service}}",
|
"check_service": "Tên dịch vụ: {{service}}",
|
||||||
|
"service_name": "Tên dịch vụ",
|
||||||
"check_not_found": "Không tìm thấy trong danh sách bộ lọc của bạn",
|
"check_not_found": "Không tìm thấy trong danh sách bộ lọc của bạn",
|
||||||
"client_confirm_block": "Bạn có muốn chặn người dùng {{ip}}?",
|
"client_confirm_block": "Bạn có muốn chặn người dùng {{ip}}?",
|
||||||
"client_confirm_unblock": "Bạn có muốn bỏ chặn người dùng {{ip}}?",
|
"client_confirm_unblock": "Bạn có muốn bỏ chặn người dùng {{ip}}?",
|
||||||
@@ -555,6 +570,12 @@
|
|||||||
"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_min_override": "Ghi đè TTL tối thiểu",
|
||||||
"cache_ttl_max_override": "Ghi đè TTL tối đa",
|
"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",
|
||||||
|
"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",
|
||||||
"filter_category_general": "Chung",
|
"filter_category_general": "Chung",
|
||||||
"filter_category_security": "Bảo mật",
|
"filter_category_security": "Bảo mật",
|
||||||
"filter_category_regional": "Khu vực",
|
"filter_category_regional": "Khu vực",
|
||||||
@@ -566,5 +587,8 @@
|
|||||||
"setup_config_to_enable_dhcp_server": "Thiết lập cấu hình để bật máy chủ DHCP",
|
"setup_config_to_enable_dhcp_server": "Thiết lập cấu hình để bật máy chủ DHCP",
|
||||||
"original_response": "Phản hồi gốc",
|
"original_response": "Phản hồi gốc",
|
||||||
"click_to_view_queries": "Nhấp để xem truy xuất",
|
"click_to_view_queries": "Nhấp để xem truy xuất",
|
||||||
"port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn</0> để giải quyết vấn đề này."
|
"port_53_faq_link": "Cổng 53 thường được sử dụng \"DNSStubListener\" hoặc \"systemd-resolved\". Vui lòng đọc <0>hướng dẫn</0> để giải quyết vấn đề này.",
|
||||||
|
"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.",
|
||||||
|
"client_not_in_allowed_clients": "Ứng dụng khách không được phép vì nó không có trong danh sách \"Ứng dụng khách được phép\".",
|
||||||
|
"experimental": "Thử nghiệm"
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "无效的 IPv4 格式",
|
"form_error_ip_format": "无效的 IPv4 格式",
|
||||||
"form_error_mac_format": "无效的 MAC 格式",
|
"form_error_mac_format": "无效的 MAC 格式",
|
||||||
"form_error_client_id_format": "无效的客户端 ID 格式",
|
"form_error_client_id_format": "无效的客户端 ID 格式",
|
||||||
|
"form_error_server_name": "无效的服务器名",
|
||||||
"form_error_positive": "必须大于 0",
|
"form_error_positive": "必须大于 0",
|
||||||
"form_error_negative": "必须大于等于 0",
|
"form_error_negative": "必须大于等于 0",
|
||||||
"range_end_error": "必须大于范围起始值",
|
"range_end_error": "必须大于范围起始值",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "自定义 IP",
|
"custom_ip": "自定义 IP",
|
||||||
"blocking_ipv4": "拦截 IPv4",
|
"blocking_ipv4": "拦截 IPv4",
|
||||||
"blocking_ipv6": "拦截 IPv6",
|
"blocking_ipv6": "拦截 IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "客户端 ID",
|
||||||
|
"client_id_placeholder": "输入客户端 ID",
|
||||||
|
"client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在 <a>这里</a>你可以了解到更多关于如何识别客户端的信息。",
|
||||||
"download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig",
|
"download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig",
|
||||||
"download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig",
|
"download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig",
|
||||||
|
"download_mobileconfig": "下载配置文件",
|
||||||
"plain_dns": "无加密DNS",
|
"plain_dns": "无加密DNS",
|
||||||
"form_enter_rate_limit": "输入限制速率",
|
"form_enter_rate_limit": "输入限制速率",
|
||||||
"rate_limit": "速度限制",
|
"rate_limit": "速度限制",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "加密配置已保存",
|
"encryption_config_saved": "加密配置已保存",
|
||||||
"encryption_server": "服务器名称",
|
"encryption_server": "服务器名称",
|
||||||
"encryption_server_enter": "输入您的域名",
|
"encryption_server_enter": "输入您的域名",
|
||||||
"encryption_server_desc": "若要使用 HTTPS ,您需要输入与 SSL 证书相匹配的服务器名称。",
|
"encryption_server_desc": "为了使用 HTTPS,请您输入与 SSL 证书或通配证书相匹配的服务器名称。如此字段未设置,服务器将要为所有域名接受 TLS 连接。",
|
||||||
"encryption_redirect": "HTTPS 自动重定向",
|
"encryption_redirect": "HTTPS 自动重定向",
|
||||||
"encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。",
|
"encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。",
|
||||||
"encryption_https": "HTTPS 端口",
|
"encryption_https": "HTTPS 端口",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "编辑客户端",
|
"client_edit": "编辑客户端",
|
||||||
"client_identifier": "标识符",
|
"client_identifier": "标识符",
|
||||||
"ip_address": "IP 地址",
|
"ip_address": "IP 地址",
|
||||||
"client_identifier_desc": "客户端可通过 IP 地址或 MAC 地址识别。请注意,如 AdGuard Home 也是 <0>DHCP 服务器</0>,则仅能将 MAC 用作标识符",
|
"client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或特殊 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里</0>您可多了解如何识别客户端。",
|
||||||
"form_enter_ip": "输入 IP",
|
"form_enter_ip": "输入 IP",
|
||||||
"form_enter_mac": "输入 MAC",
|
"form_enter_mac": "输入 MAC",
|
||||||
"form_enter_id": "输入标识符",
|
"form_enter_id": "输入标识符",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支持 <1>DNS-over-HTTPS</1>。",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支持 <1>DNS-over-HTTPS</1>。",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支持 <1>DNS-over-HTTPS</1>。",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支持 <1>DNS-over-HTTPS</1>。",
|
||||||
"setup_dns_privacy_other_5": "您可以从 <0>这里</0> 和 <1>这里</1> 找到更多的实施方案。",
|
"setup_dns_privacy_other_5": "您可以从 <0>这里</0> 和 <1>这里</1> 找到更多的实施方案。",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置",
|
||||||
"setup_dns_notice": "为了使用 <1>DNS-over-HTTPS</1> 或者 <1>DNS-over-TLS</1> ,您需要在 AdGuard Home 设置中 <0>配置加密</0> 。",
|
"setup_dns_notice": "为了使用 <1>DNS-over-HTTPS</1> 或者 <1>DNS-over-TLS</1> ,您需要在 AdGuard Home 设置中 <0>配置加密</0> 。",
|
||||||
"rewrite_added": "已成功添加 \"{{key}}\" 的 DNS 重写",
|
"rewrite_added": "已成功添加 \"{{key}}\" 的 DNS 重写",
|
||||||
"rewrite_deleted": "已成功删除 \"{{key}}\" 的 DNS 重写",
|
"rewrite_deleted": "已成功删除 \"{{key}}\" 的 DNS 重写",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP地址:{{ip}}",
|
"check_ip": "IP地址:{{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "原因:{{reason}}",
|
"check_reason": "原因:{{reason}}",
|
||||||
"check_rule": "规则:{{rule}}",
|
|
||||||
"check_service": "服务名称:{{service}}",
|
"check_service": "服务名称:{{service}}",
|
||||||
"service_name": "服务名称",
|
"service_name": "服务名称",
|
||||||
"check_not_found": "未在您的筛选列表中找到",
|
"check_not_found": "未在您的筛选列表中找到",
|
||||||
|
|||||||
@@ -269,7 +269,6 @@
|
|||||||
"source_label": "來源",
|
"source_label": "來源",
|
||||||
"found_in_known_domain_db": "在已知網域資料庫中找到。",
|
"found_in_known_domain_db": "在已知網域資料庫中找到。",
|
||||||
"category_label": "類別",
|
"category_label": "類別",
|
||||||
"rule_label": "規則",
|
|
||||||
"list_label": "清單",
|
"list_label": "清單",
|
||||||
"unknown_filter": "未知過濾器 {{filterId}}",
|
"unknown_filter": "未知過濾器 {{filterId}}",
|
||||||
"known_tracker": "已知追蹤器",
|
"known_tracker": "已知追蹤器",
|
||||||
@@ -330,7 +329,6 @@
|
|||||||
"encryption_config_saved": "加密設定已儲存",
|
"encryption_config_saved": "加密設定已儲存",
|
||||||
"encryption_server": "伺服器名稱",
|
"encryption_server": "伺服器名稱",
|
||||||
"encryption_server_enter": "輸入您的網域名稱",
|
"encryption_server_enter": "輸入您的網域名稱",
|
||||||
"encryption_server_desc": "要使用 HTTPS,您必須輸入與您 SSL 憑證相符的伺服器名稱。",
|
|
||||||
"encryption_redirect": "自動重新導向到 HTTPS",
|
"encryption_redirect": "自動重新導向到 HTTPS",
|
||||||
"encryption_redirect_desc": "如果啟用,AdGuard Home 將會自動導向 HTTP 到 HTTPS。",
|
"encryption_redirect_desc": "如果啟用,AdGuard Home 將會自動導向 HTTP 到 HTTPS。",
|
||||||
"encryption_https": "HTTPS 連接埠",
|
"encryption_https": "HTTPS 連接埠",
|
||||||
@@ -386,7 +384,6 @@
|
|||||||
"client_edit": "編輯用戶端",
|
"client_edit": "編輯用戶端",
|
||||||
"client_identifier": "識別碼",
|
"client_identifier": "識別碼",
|
||||||
"ip_address": "IP 位址",
|
"ip_address": "IP 位址",
|
||||||
"client_identifier_desc": "可通過 IP 地址、CIDR、MAC 地址來辨識使用者裝置。注意:必須使用 AdGuard Home 內建 <0>DHCP 伺服器</0> 才能偵測 MAC 地址。",
|
|
||||||
"form_enter_ip": "輸入 IP",
|
"form_enter_ip": "輸入 IP",
|
||||||
"form_enter_mac": "輸入 MAC 地址",
|
"form_enter_mac": "輸入 MAC 地址",
|
||||||
"form_enter_id": "輸入識別碼",
|
"form_enter_id": "輸入識別碼",
|
||||||
@@ -529,7 +526,6 @@
|
|||||||
"check_ip": "IP 位址:{{ip}}",
|
"check_ip": "IP 位址:{{ip}}",
|
||||||
"check_cname": "CNAME:{{cname}}",
|
"check_cname": "CNAME:{{cname}}",
|
||||||
"check_reason": "原因:{{reason}}",
|
"check_reason": "原因:{{reason}}",
|
||||||
"check_rule": "規則:{{rule}}",
|
|
||||||
"check_service": "服務名稱:{{service}}",
|
"check_service": "服務名稱:{{service}}",
|
||||||
"service_name": "服務名稱",
|
"service_name": "服務名稱",
|
||||||
"check_not_found": "未在您的過濾清單中找到",
|
"check_not_found": "未在您的過濾清單中找到",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "用戶端設定",
|
"client_settings": "用戶端設定",
|
||||||
"example_upstream_reserved": "您可以<0>為特定網域</0>指定上游 DNS",
|
"example_upstream_reserved": "您可<0>對於特定的網域</0>明確指定 DNS 上游",
|
||||||
"example_upstream_comment": "您可明確指定註解",
|
"example_upstream_comment": "您可明確指定註解",
|
||||||
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析網域",
|
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析網域",
|
||||||
"parallel_requests": "並行的請求",
|
"parallel_requests": "並行的請求",
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"form_error_ip_format": "無效的 IP 格式",
|
"form_error_ip_format": "無效的 IP 格式",
|
||||||
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
||||||
"form_error_client_id_format": "無效的用戶端 ID 格式",
|
"form_error_client_id_format": "無效的用戶端 ID 格式",
|
||||||
|
"form_error_server_name": "無效的伺服器名稱",
|
||||||
"form_error_positive": "必須大於 0",
|
"form_error_positive": "必須大於 0",
|
||||||
"form_error_negative": "必須等於或大於 0",
|
"form_error_negative": "必須等於或大於 0",
|
||||||
"range_end_error": "必須大於起始範圍",
|
"range_end_error": "必須大於起始範圍",
|
||||||
@@ -247,10 +248,16 @@
|
|||||||
"custom_ip": "自訂的 IP",
|
"custom_ip": "自訂的 IP",
|
||||||
"blocking_ipv4": "封鎖 IPv4",
|
"blocking_ipv4": "封鎖 IPv4",
|
||||||
"blocking_ipv6": "封鎖 IPv6",
|
"blocking_ipv6": "封鎖 IPv6",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
|
"client_id": "用戶端 ID",
|
||||||
|
"client_id_placeholder": "輸入用戶端 ID",
|
||||||
|
"client_id_desc": "不同的用戶端可根據特殊的用戶端 ID 被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
|
||||||
"download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig",
|
"download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig",
|
||||||
"download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig",
|
"download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig",
|
||||||
|
"download_mobileconfig": "下載配置檔案",
|
||||||
"plain_dns": "一般的 DNS",
|
"plain_dns": "一般的 DNS",
|
||||||
"form_enter_rate_limit": "輸入速率限制",
|
"form_enter_rate_limit": "輸入速率限制",
|
||||||
"rate_limit": "速率限制",
|
"rate_limit": "速率限制",
|
||||||
@@ -330,7 +337,7 @@
|
|||||||
"encryption_config_saved": "加密配置被儲存",
|
"encryption_config_saved": "加密配置被儲存",
|
||||||
"encryption_server": "伺服器名稱",
|
"encryption_server": "伺服器名稱",
|
||||||
"encryption_server_enter": "輸入您的域名",
|
"encryption_server_enter": "輸入您的域名",
|
||||||
"encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證相符的伺服器名稱。",
|
"encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證或萬用字元憑證相符的伺服器名稱。如果此欄位未被設定,它將接受向任何網域的傳輸層安全性協定(TLS)連線。",
|
||||||
"encryption_redirect": "自動地重新導向到 HTTPS",
|
"encryption_redirect": "自動地重新導向到 HTTPS",
|
||||||
"encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
|
"encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
|
||||||
"encryption_https": "HTTPS 連接埠",
|
"encryption_https": "HTTPS 連接埠",
|
||||||
@@ -386,7 +393,7 @@
|
|||||||
"client_edit": "編輯用戶端",
|
"client_edit": "編輯用戶端",
|
||||||
"client_identifier": "識別碼",
|
"client_identifier": "識別碼",
|
||||||
"ip_address": "IP 位址",
|
"ip_address": "IP 位址",
|
||||||
"client_identifier_desc": "用戶端可被 IP 位址、無類別網域間路由(CIDR)或媒體存取控制(MAC)位址識別。請注意,只要 AdGuard Home 也是<0>動態主機設定協定(DHCP)伺服器</0>,使用 MAC 作為識別碼是可能的",
|
"client_identifier_desc": "用戶端可根據 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或特殊的用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
|
||||||
"form_enter_ip": "輸入 IP",
|
"form_enter_ip": "輸入 IP",
|
||||||
"form_enter_mac": "輸入媒體存取控制(MAC)",
|
"form_enter_mac": "輸入媒體存取控制(MAC)",
|
||||||
"form_enter_id": "輸入識別碼",
|
"form_enter_id": "輸入識別碼",
|
||||||
@@ -430,6 +437,7 @@
|
|||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支援 <1>DNS-over-HTTPS</1>。",
|
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支援 <1>DNS-over-HTTPS</1>。",
|
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||||
"setup_dns_privacy_other_5": "在<0>這裡</0>和<1>這裡</1>,您將發現更多的執行。",
|
"setup_dns_privacy_other_5": "在<0>這裡</0>和<1>這裡</1>,您將發現更多的執行。",
|
||||||
|
"setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置",
|
||||||
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
|
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
|
||||||
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
|
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
|
||||||
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
|
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
|
||||||
@@ -529,7 +537,6 @@
|
|||||||
"check_ip": "IP 位址:{{ip}}",
|
"check_ip": "IP 位址:{{ip}}",
|
||||||
"check_cname": "正規名稱(CNAME):{{cname}}",
|
"check_cname": "正規名稱(CNAME):{{cname}}",
|
||||||
"check_reason": "原因:{{reason}}",
|
"check_reason": "原因:{{reason}}",
|
||||||
"check_rule": "規則:{{rule}}",
|
|
||||||
"check_service": "服務名稱:{{service}}",
|
"check_service": "服務名稱:{{service}}",
|
||||||
"service_name": "服務名稱",
|
"service_name": "服務名稱",
|
||||||
"check_not_found": "未在您的過濾器中被找到",
|
"check_not_found": "未在您的過濾器中被找到",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
--gray-d8: #d8d8d8;
|
--gray-d8: #d8d8d8;
|
||||||
--gray-f3: #f3f3f3;
|
--gray-f3: #f3f3f3;
|
||||||
--font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
--font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||||
|
--font-size-disable-autozoom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -13,9 +14,10 @@ body {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
font-size: 16px !important;
|
font-size: var(--font-size-disable-autozoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
font-size: 0.9375rem;
|
font-size: var(--font-size-disable-autozoom);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -534,6 +534,7 @@ export const BLOCK_ACTIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SCHEME_TO_PROTOCOL_MAP = {
|
export const SCHEME_TO_PROTOCOL_MAP = {
|
||||||
|
dnscrypt: 'dnscrypt',
|
||||||
doh: 'dns_over_https',
|
doh: 'dns_over_https',
|
||||||
dot: 'dns_over_tls',
|
dot: 'dns_over_tls',
|
||||||
doq: 'dns_over_quic',
|
doq: 'dns_over_quic',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
font-size: 16px !important;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
font-size: 16px !important;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"stylelint-webpack-plugin": "^2.1.1",
|
"stylelint-webpack-plugin": "^2.1.1",
|
||||||
"terser-webpack-plugin": "^5.0.0",
|
"terser-webpack-plugin": "^5.0.0",
|
||||||
"ts-loader": "^8.0.6",
|
"ts-loader": "^8.0.6",
|
||||||
"ts-morph": "^8.1.2",
|
"ts-morph": "^10.0.1",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ export const trimQuotes = (str: string) => {
|
|||||||
return str.replace(/\'|\"/g, '');
|
return str.replace(/\'|\"/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GENERATOR_ENTITY_ALLIAS = 'Entities/';
|
export const GENERATOR_ENTITY_ALLIAS = 'Entities/';
|
||||||
|
export const BAD_REQUES_HELPER = 'BadRequesHelper';
|
||||||
@@ -4,9 +4,10 @@ import { OPEN_API_PATH } from '../consts';
|
|||||||
|
|
||||||
import EntitiesGenerator from './src/generateEntities';
|
import EntitiesGenerator from './src/generateEntities';
|
||||||
import ApisGenerator from './src/generateApis';
|
import ApisGenerator from './src/generateApis';
|
||||||
|
import { OpenApi } from './src/utils';
|
||||||
|
|
||||||
|
|
||||||
const generateApi = (openApi: Record<string, any>) => {
|
const generateApi = (openApi: OpenApi) => {
|
||||||
const ent = new EntitiesGenerator(openApi);
|
const ent = new EntitiesGenerator(openApi);
|
||||||
ent.save();
|
ent.save();
|
||||||
|
|
||||||
@@ -14,5 +15,5 @@ const generateApi = (openApi: Record<string, any>) => {
|
|||||||
api.save();
|
api.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
const openApiFile = fs.readFileSync(OPEN_API_PATH, 'utf8');
|
const openApiFile = fs.readFileSync('./scripts/generator/v1.yaml', 'utf8');
|
||||||
generateApi(YAML.parse(openApiFile));
|
generateApi(YAML.parse(openApiFile));
|
||||||
|
|||||||
@@ -2,15 +2,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { stringify } from 'qs';
|
import { number } from 'prop-types';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as morph from 'ts-morph';
|
import * as morph from 'ts-morph';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
API_DIR as API_DIR_CONST,
|
API_DIR as API_DIR_CONST,
|
||||||
|
BAD_REQUES_HELPER,
|
||||||
GENERATOR_ENTITY_ALLIAS,
|
GENERATOR_ENTITY_ALLIAS,
|
||||||
} from '../../consts';
|
} from '../../consts';
|
||||||
import { toCamel, capitalize, schemaParamParser } from './utils';
|
import { toCamel, capitalize, schemaParamParser, OpenApi, uncapitalize, RequestBody } from './utils';
|
||||||
|
|
||||||
|
|
||||||
const API_DIR = path.resolve(API_DIR_CONST);
|
const API_DIR = path.resolve(API_DIR_CONST);
|
||||||
@@ -20,11 +21,15 @@ if (!fs.existsSync(API_DIR)) {
|
|||||||
|
|
||||||
const { Project, QuoteKind } = morph;
|
const { Project, QuoteKind } = morph;
|
||||||
|
|
||||||
|
enum PROCESS_AS {
|
||||||
|
JSON = 'JSON',
|
||||||
|
TEXT = 'TEXT',
|
||||||
|
EMPTY = 'EMPTY',
|
||||||
|
}
|
||||||
|
|
||||||
class ApiGenerator {
|
class ApiGenerator {
|
||||||
project = new Project({
|
project = new Project({
|
||||||
tsConfigFilePath: './tsconfig.json',
|
tsConfigFilePath: './tsconfig.json',
|
||||||
addFilesFromTsConfig: false,
|
|
||||||
manipulationSettings: {
|
manipulationSettings: {
|
||||||
quoteKind: QuoteKind.Single,
|
quoteKind: QuoteKind.Single,
|
||||||
usePrefixAndSuffixTextForRename: false,
|
usePrefixAndSuffixTextForRename: false,
|
||||||
@@ -32,7 +37,7 @@ class ApiGenerator {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
openapi: Record<string, any>;
|
openapi: OpenApi;
|
||||||
|
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
|
|
||||||
@@ -47,20 +52,28 @@ class ApiGenerator {
|
|||||||
|
|
||||||
apis: morph.SourceFile[] = [];
|
apis: morph.SourceFile[] = [];
|
||||||
|
|
||||||
constructor(openapi: Record<string, any>) {
|
methods = ['patch', 'delete', 'post', 'get', 'put', 'head', 'options', 'trace'];
|
||||||
|
|
||||||
|
constructor(openapi: OpenApi) {
|
||||||
this.openapi = openapi;
|
this.openapi = openapi;
|
||||||
this.paths = openapi.paths;
|
this.paths = openapi.paths;
|
||||||
this.serverUrl = openapi.servers[0].url;
|
this.serverUrl = openapi.servers[0].url;
|
||||||
|
|
||||||
Object.keys(this.paths).forEach((pathKey) => {
|
Object.keys(this.paths).forEach((pathKey) => {
|
||||||
Object.keys(this.paths[pathKey]).forEach((method) => {
|
Object.keys(this.paths[pathKey]).filter((method) => this.methods.includes(method)).forEach((method) => {
|
||||||
const {
|
const {
|
||||||
tags, operationId, parameters, responses, requestBody, security,
|
tags, operationId, responses, requestBody, security, "x-skip-web-api": skip
|
||||||
} = this.paths[pathKey][method];
|
} = this.paths[pathKey][method];
|
||||||
const controller = toCamel((tags ? tags[0] : pathKey.split('/')[1]).replace('-controller', ''));
|
const parameters = this.paths[pathKey][method].parameters || this.paths[pathKey].parameters;
|
||||||
|
const controller = toCamel((tags ? tags[0] : pathKey.split('/')[1]));
|
||||||
|
if (skip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!operationId) {
|
||||||
|
console.log(pathKey);
|
||||||
|
}
|
||||||
if (this.controllers[controller]) {
|
if (this.controllers[controller]) {
|
||||||
this.controllers[controller][operationId] = {
|
this.controllers[controller][uncapitalize(operationId)] = {
|
||||||
parameters,
|
parameters,
|
||||||
responses,
|
responses,
|
||||||
method,
|
method,
|
||||||
@@ -69,7 +82,7 @@ class ApiGenerator {
|
|||||||
pathKey: pathKey.replace(/{/g, '${'),
|
pathKey: pathKey.replace(/{/g, '${'),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.controllers[controller] = { [operationId]: {
|
this.controllers[controller] = { [uncapitalize(operationId)]: {
|
||||||
parameters,
|
parameters,
|
||||||
responses,
|
responses,
|
||||||
method,
|
method,
|
||||||
@@ -97,7 +110,7 @@ class ApiGenerator {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// const schemaProperties = schemas[schemaName].properties;
|
// const schemaProperties = schemas[schemaName].properties;
|
||||||
const importEntities: any[] = [];
|
const importEntities: { type: string, isClass: boolean }[] = [];
|
||||||
|
|
||||||
// add api class to file
|
// add api class to file
|
||||||
const apiClass = apiFile.addClass({
|
const apiClass = apiFile.addClass({
|
||||||
@@ -111,29 +124,34 @@ class ApiGenerator {
|
|||||||
// for each operation add fetcher
|
// for each operation add fetcher
|
||||||
operationList.forEach((operation) => {
|
operationList.forEach((operation) => {
|
||||||
const {
|
const {
|
||||||
requestBody, responses, parameters, method, pathKey, security,
|
requestBody, responses, parameters, method, pathKey,
|
||||||
} = controllerOperations[operation];
|
} = controllerOperations[operation];
|
||||||
|
|
||||||
const queryParams: any[] = []; // { name, type }
|
const queryParams: { name: string, type: string, hasQuestionToken: boolean }[] = [];
|
||||||
const bodyParam: any[] = []; // { name, type }
|
const bodyParam: { name: string, countedType: string, type?: string, isClass?: boolean, hasQuestionToken: boolean }[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
let contentType: string = '';
|
||||||
|
|
||||||
let hasResponseBodyType: /* boolean | ReturnType<schemaParamParser> */ false | [string, boolean, boolean, boolean, boolean] = false;
|
|
||||||
let contentType = '';
|
|
||||||
if (parameters) {
|
if (parameters) {
|
||||||
parameters.forEach((p: any) => {
|
parameters.forEach((link: {$ref: string}) => {
|
||||||
const [
|
const temp = link.$ref.split('/').pop()
|
||||||
pType, isArray, isClass, isImport,
|
const parameter = this.openapi.components.parameters[temp!];
|
||||||
] = schemaParamParser(p.schema, this.openapi);
|
|
||||||
|
const {
|
||||||
|
type, isArray, isClass, isImport,
|
||||||
|
} = schemaParamParser(parameter.schema, this.openapi);
|
||||||
|
|
||||||
if (isImport) {
|
if (isImport) {
|
||||||
importEntities.push({ type: pType, isClass });
|
importEntities.push({ type, isClass });
|
||||||
}
|
}
|
||||||
if (p.in === 'query') {
|
if (parameter.in === 'query') {
|
||||||
queryParams.push({
|
queryParams.push({
|
||||||
name: p.name, type: `${pType}${isArray ? '[]' : ''}`, hasQuestionToken: !p.required });
|
name: parameter.name, type: `${type}${isArray ? '[]' : ''}`, hasQuestionToken: !parameter.required });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryParams.length > 0) {
|
if (queryParams.length > 0) {
|
||||||
const imp = apiFile.getImportDeclaration((i) => {
|
const imp = apiFile.getImportDeclaration((i) => {
|
||||||
return i.getModuleSpecifierValue() === 'qs';
|
return i.getModuleSpecifierValue() === 'qs';
|
||||||
@@ -144,62 +162,120 @@ class ApiGenerator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestBody) {
|
if (requestBody) {
|
||||||
let content = requestBody.content;
|
|
||||||
const { $ref }: { $ref: string } = requestBody;
|
const { $ref }: { $ref: string } = requestBody;
|
||||||
|
|
||||||
if (!content && $ref) {
|
const name = $ref.split('/').pop();
|
||||||
const name = $ref.split('/').pop() as string;
|
const { content, required } = this.openapi.components.requestBodies[name!];
|
||||||
content = this.openapi.components.requestBodies[name].content;
|
|
||||||
}
|
|
||||||
|
|
||||||
[contentType] = Object.keys(content);
|
[contentType] = Object.keys(content);
|
||||||
const data = content[contentType];
|
const data = content[contentType as keyof RequestBody['content']]!;
|
||||||
|
|
||||||
const [
|
const {
|
||||||
pType, isArray, isClass, isImport,
|
type, isArray, isClass, isImport,
|
||||||
] = schemaParamParser(data.schema, this.openapi);
|
} = schemaParamParser(data.schema, this.openapi);
|
||||||
|
|
||||||
if (isImport) {
|
if (isImport) {
|
||||||
importEntities.push({ type: pType, isClass });
|
importEntities.push({ type: type, isClass });
|
||||||
bodyParam.push({ name: pType.toLowerCase(), type: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`, isClass, pType });
|
bodyParam.push({
|
||||||
|
name: type.toLowerCase(),
|
||||||
|
countedType: `${isClass ? 'I' : ''}${type}${isArray ? '[]' : ''}`,
|
||||||
|
isClass,
|
||||||
|
type,
|
||||||
|
hasQuestionToken: !required
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
bodyParam.push({ name: 'data', type: `${pType}${isArray ? '[]' : ''}` });
|
bodyParam.push({
|
||||||
|
name: 'data',
|
||||||
|
countedType: `${type}${isArray ? '[]' : ''}`,
|
||||||
|
hasQuestionToken: !required });
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (responses['200']) {
|
|
||||||
const { content, headers } = responses['200'];
|
|
||||||
if (content && (content['*/*'] || content['application/json'])) {
|
|
||||||
const { schema, examples } = content['*/*'] || content['application/json'];
|
|
||||||
|
|
||||||
if (!schema) {
|
const responsesCodes = Object.keys(responses);
|
||||||
process.exit(0);
|
const responsesSchema = responsesCodes.map((code) => {
|
||||||
|
const refLink = responses[code].$ref.split('/').pop();
|
||||||
|
const ref = this.openapi.components.responses[refLink];
|
||||||
|
|
||||||
|
interface ResponseSchema {
|
||||||
|
code: number,
|
||||||
|
[PROCESS_AS.JSON]?: ReturnType<typeof schemaParamParser>;
|
||||||
|
[PROCESS_AS.TEXT]?: {
|
||||||
|
schema?: ReturnType<typeof schemaParamParser>;
|
||||||
|
xErrorCode?: string;
|
||||||
|
onlyText: boolean;
|
||||||
}
|
}
|
||||||
|
[PROCESS_AS.EMPTY]?: boolean;
|
||||||
const propType = schemaParamParser(schema, this.openapi);
|
|
||||||
const [pType, , isClass, isImport] = propType;
|
|
||||||
|
|
||||||
if (isImport) {
|
|
||||||
importEntities.push({ type: pType, isClass });
|
|
||||||
}
|
|
||||||
hasResponseBodyType = propType;
|
|
||||||
}
|
}
|
||||||
}
|
const responseSchema: ResponseSchema = { code: Number(code) };
|
||||||
let returnType = '';
|
|
||||||
if (hasResponseBodyType) {
|
if (!ref.content) {
|
||||||
const [pType, isArray, isClass] = hasResponseBodyType as any;
|
responseSchema[PROCESS_AS.EMPTY] = true;
|
||||||
let data = `Promise<${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
return responseSchema;
|
||||||
returnType = data;
|
}
|
||||||
} else {
|
if (ref.content?.['application/json']) {
|
||||||
returnType = 'Promise<number';
|
const { schema } = ref.content['application/json'];
|
||||||
}
|
responseSchema[PROCESS_AS.JSON] = schemaParamParser(schema, this.openapi);
|
||||||
const shouldValidate = bodyParam.filter(b => b.isClass);
|
}
|
||||||
if (shouldValidate.length > 0) {
|
if (ref.content?.['text/palin']) {
|
||||||
returnType += ' | string[]';
|
const {
|
||||||
}
|
"x-error-class": xErrorClass,
|
||||||
// append Error to default type return;
|
"x-error-code": xErrorCode,
|
||||||
returnType += ' | Error>';
|
} = ref.content['text/palin'];
|
||||||
|
if (xErrorClass) {
|
||||||
|
const schemaLink = xErrorClass.split('/').pop();
|
||||||
|
const schema = this.openapi.components.schemas[schemaLink!];
|
||||||
|
responseSchema[PROCESS_AS.TEXT] = {
|
||||||
|
schema: schemaParamParser(schema, this.openapi),
|
||||||
|
xErrorCode,
|
||||||
|
onlyText: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseSchema[PROCESS_AS.TEXT] = { onlyText: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responseSchema;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let returnTypes = new Set();
|
||||||
|
|
||||||
|
bodyParam.forEach((param) => {
|
||||||
|
if (param.isClass) {
|
||||||
|
returnTypes.add(BAD_REQUES_HELPER);
|
||||||
|
importEntities.push({ type: BAD_REQUES_HELPER, isClass: true });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
responsesSchema.forEach((responseSchema) => {
|
||||||
|
if (responseSchema[PROCESS_AS.JSON]) {
|
||||||
|
const { type, isClass, isImport } = responseSchema[PROCESS_AS.JSON]!;
|
||||||
|
returnTypes.add(type);
|
||||||
|
if (isImport) {
|
||||||
|
importEntities.push({ type: type, isClass });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseSchema[PROCESS_AS.TEXT]) {
|
||||||
|
const { onlyText, schema } = responseSchema[PROCESS_AS.TEXT]!;
|
||||||
|
if (onlyText) {
|
||||||
|
returnTypes.add('string');
|
||||||
|
} else {
|
||||||
|
const { type, isClass, isImport } = schema!;
|
||||||
|
returnTypes.add(type);
|
||||||
|
if (isImport) {
|
||||||
|
importEntities.push({ type, isClass });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseSchema[PROCESS_AS.EMPTY]) {
|
||||||
|
returnTypes.add('number');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
returnTypes.add('undefined');
|
||||||
|
const returnType = `Promise<${Array.from(returnTypes).join(' | ')}>`;
|
||||||
|
|
||||||
const fetcher = apiClass.addMethod({
|
const fetcher = apiClass.addMethod({
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
@@ -211,23 +287,19 @@ class ApiGenerator {
|
|||||||
fetcher.addParameters(params);
|
fetcher.addParameters(params);
|
||||||
|
|
||||||
fetcher.setBodyText((w) => {
|
fetcher.setBodyText((w) => {
|
||||||
// Add data to URLSearchParams
|
if (contentType === 'application/json') {
|
||||||
if (contentType === 'text/plain') {
|
const shouldValidate = bodyParam.filter(b => b.isClass);
|
||||||
bodyParam.forEach((b) => {
|
|
||||||
w.writeLine(`const params = String(${b.name});`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (shouldValidate.length > 0) {
|
if (shouldValidate.length > 0) {
|
||||||
w.writeLine(`const haveError: string[] = [];`);
|
w.writeLine(`const haveError: string[] = [];`);
|
||||||
shouldValidate.forEach((b) => {
|
shouldValidate.forEach((b) => {
|
||||||
w.writeLine(`const ${b.name}Valid = new ${b.pType}(${b.name});`);
|
w.writeLine(`haveError.push(...${b.name}.validate());`);
|
||||||
w.writeLine(`haveError.push(...${b.name}Valid.validate());`);
|
|
||||||
});
|
});
|
||||||
w.writeLine(`if (haveError.length > 0) {`);
|
w.writeLine(`if (haveError.length > 0) {`);
|
||||||
w.writeLine(` return Promise.resolve(haveError);`)
|
w.writeLine(` return Promise.resolve(new ${BAD_REQUES_HELPER}(haveError));`)
|
||||||
w.writeLine(`}`);
|
w.writeLine(`}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch return of fetch in case on queryParams
|
// Switch return of fetch in case on queryParams
|
||||||
if (queryParams.length > 0) {
|
if (queryParams.length > 0) {
|
||||||
w.writeLine('const queryParams = {');
|
w.writeLine('const queryParams = {');
|
||||||
@@ -243,37 +315,36 @@ class ApiGenerator {
|
|||||||
w.writeLine(` method: '${method.toUpperCase()}',`);
|
w.writeLine(` method: '${method.toUpperCase()}',`);
|
||||||
|
|
||||||
// add Fetch options
|
// add Fetch options
|
||||||
if (contentType && contentType !== 'multipart/form-data') {
|
|
||||||
w.writeLine(' headers: {');
|
|
||||||
w.writeLine(` 'Content-Type': '${contentType}',`);
|
|
||||||
w.writeLine(' },');
|
|
||||||
}
|
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
switch (contentType) {
|
w.writeLine(` body: JSON.stringify(${bodyParam.map((b) => b.isClass ? `${b.name}.serialize()` : b.name).join(', ')}),`);
|
||||||
case 'text/plain':
|
}
|
||||||
w.writeLine(' body: params,');
|
|
||||||
break;
|
w.writeLine('}).then(async (res) => {');
|
||||||
default:
|
responsesSchema.forEach((responseSchema) => {
|
||||||
w.writeLine(` body: JSON.stringify(${bodyParam.map((b) => b.isClass ? `${b.name}Valid.serialize()` : b.name).join(', ')}),`);
|
const { code } = responseSchema;
|
||||||
break;
|
w.writeLine(` if (res.status === ${code}) {`);
|
||||||
|
if (responseSchema[PROCESS_AS.EMPTY]) {
|
||||||
|
w.writeLine(' return res.status;');
|
||||||
}
|
}
|
||||||
}
|
if (responseSchema[PROCESS_AS.TEXT]?.onlyText) {
|
||||||
|
w.writeLine(' return res.text();')
|
||||||
// Handle response
|
}
|
||||||
if (hasResponseBodyType) {
|
if (responseSchema[PROCESS_AS.JSON] && responseSchema[PROCESS_AS.TEXT]) {
|
||||||
w.writeLine('}).then(async (res) => {');
|
const { type } = responseSchema[PROCESS_AS.JSON]!;
|
||||||
w.writeLine(' if (res.status === 200) {');
|
const { schema, xErrorCode } = responseSchema[PROCESS_AS.TEXT]!;
|
||||||
w.writeLine(' return res.json();');
|
const { type: errType } = schema!;
|
||||||
} else {
|
w.writeLine(' try {');
|
||||||
w.writeLine('}).then(async (res) => {');
|
w.writeLine(` return new ${type}(await res.json());`);
|
||||||
w.writeLine(' if (res.status === 200) {');
|
w.writeLine(' } catch {');
|
||||||
w.writeLine(' return res.status;');
|
w.writeLine(` return new ${errType}({ msg: await res.text() code: ${xErrorCode}} as any);`);
|
||||||
}
|
w.writeLine(' }');
|
||||||
|
}
|
||||||
// Handle Error
|
if (responseSchema[PROCESS_AS.JSON]) {
|
||||||
w.writeLine(' } else {');
|
const { type } = responseSchema[PROCESS_AS.JSON]!;
|
||||||
w.writeLine(' return new Error(String(res.status));');
|
w.writeLine(` return new ${type}(await res.json());`);
|
||||||
w.writeLine(' }');
|
}
|
||||||
|
w.writeLine(` }`);
|
||||||
|
})
|
||||||
w.writeLine('})');
|
w.writeLine('})');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -288,17 +359,16 @@ class ApiGenerator {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
imports.sort((a,b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
imports.sort((a,b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||||
const { type: pType, isClass } = ie;
|
const { type: type, isClass } = ie;
|
||||||
if (isClass) {
|
if (isClass) {
|
||||||
apiFile.addImportDeclaration({
|
apiFile.addImportDeclaration({
|
||||||
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`,
|
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${type}`,
|
||||||
defaultImport: pType,
|
defaultImport: type,
|
||||||
namedImports: [`I${pType}`],
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
apiFile.addImportDeclaration({
|
apiFile.addImportDeclaration({
|
||||||
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`,
|
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${type}`,
|
||||||
namedImports: [pType],
|
namedImports: [type],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import * as path from 'path';
|
|||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as morph from 'ts-morph';
|
import * as morph from 'ts-morph';
|
||||||
|
|
||||||
import { ENT_DIR } from '../../consts';
|
import { ENT_DIR, BAD_REQUES_HELPER } from '../../consts';
|
||||||
import { TYPES, toCamel, schemaParamParser, uncapitalize } from './utils';
|
import { TYPES, toCamel, schemaParamParser, capitalize, OpenApi, Schema } from './utils';
|
||||||
|
|
||||||
const { Project, QuoteKind } = morph;
|
const { Project, QuoteKind } = morph;
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ if (!fs.existsSync(EntDir)) {
|
|||||||
class EntitiesGenerator {
|
class EntitiesGenerator {
|
||||||
project = new Project({
|
project = new Project({
|
||||||
tsConfigFilePath: './tsconfig.json',
|
tsConfigFilePath: './tsconfig.json',
|
||||||
addFilesFromTsConfig: false,
|
|
||||||
manipulationSettings: {
|
manipulationSettings: {
|
||||||
quoteKind: QuoteKind.Single,
|
quoteKind: QuoteKind.Single,
|
||||||
usePrefixAndSuffixTextForRename: false,
|
usePrefixAndSuffixTextForRename: false,
|
||||||
@@ -25,491 +24,480 @@ class EntitiesGenerator {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
openapi: Record<string, any>;
|
openapi: OpenApi;
|
||||||
|
|
||||||
schemas: Record<string, any>;
|
schemas: Record<string, Schema>;
|
||||||
|
|
||||||
schemaNames: string[];
|
schemaNames: string[];
|
||||||
|
|
||||||
entities: morph.SourceFile[] = [];
|
entities: morph.SourceFile[] = [];
|
||||||
|
|
||||||
constructor(openapi: Record<string, any>) {
|
constructor(openapi: OpenApi) {
|
||||||
this.openapi = openapi;
|
this.openapi = openapi;
|
||||||
this.schemas = openapi.components.schemas;
|
this.schemas = openapi.components.schemas;
|
||||||
this.schemaNames = Object.keys(this.schemas);
|
this.schemaNames = Object.keys(this.schemas);
|
||||||
this.generateEntities();
|
this.generateEntities();
|
||||||
|
this.generateUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
generateUtils = () => {
|
||||||
|
const helperFile = this.project.createSourceFile(`${EntDir}/${BAD_REQUES_HELPER}.ts`);
|
||||||
|
helperFile.addImportDeclaration({
|
||||||
|
moduleSpecifier: `./BadRequestResp`,
|
||||||
|
defaultImport: 'BadRequestResp',
|
||||||
|
});
|
||||||
|
helperFile.addImportDeclaration({
|
||||||
|
moduleSpecifier: `./ErrorCode`,
|
||||||
|
namedImports: ['ErrorCode'],
|
||||||
|
});
|
||||||
|
const helperClass = helperFile.addClass({
|
||||||
|
name: 'BadRequestHelper',
|
||||||
|
isDefaultExport: true,
|
||||||
|
extends: 'BadRequestResp',
|
||||||
|
properties: [{
|
||||||
|
type: 'string[]',
|
||||||
|
name: 'fields'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
const helperConstructor = helperClass.addConstructor({
|
||||||
|
parameters: [{
|
||||||
|
type: 'string[]',
|
||||||
|
name: 'fields'
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
helperConstructor.setBodyText((w) => {
|
||||||
|
w.writeLine('super({ code: ErrorCode.JSN001, msg: \'Wrong fields value\' });');
|
||||||
|
w.writeLine('this.fields = fields;')
|
||||||
|
});
|
||||||
|
this.entities.push(helperFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateEntities = () => {
|
generateEntities = () => {
|
||||||
this.schemaNames.forEach(this.generateEntity);
|
this.schemaNames.forEach(this.generateEntity);
|
||||||
};
|
};
|
||||||
|
|
||||||
generateEntity = (sName: string) => {
|
generateEntity = (schemaName: string) => {
|
||||||
const { properties, type, oneOf } = this.schemas[sName];
|
const { properties, type, oneOf, enum: en } = this.schemas[schemaName];
|
||||||
const notAClass = !properties && TYPES[type as keyof typeof TYPES];
|
const notAClass = !properties && TYPES[type as keyof typeof TYPES];
|
||||||
|
|
||||||
if (oneOf) {
|
if (oneOf) {
|
||||||
this.generateOneOf(sName);
|
this.generateOneOf(schemaName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (en) {
|
||||||
|
this.generateEnum(schemaName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (notAClass) {
|
if (notAClass) {
|
||||||
this.generateEnum(sName);
|
this.generatePrimitive(schemaName)
|
||||||
} else {
|
} else {
|
||||||
this.generateClass(sName);
|
this.generateClass(schemaName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
generateEnum = (sName: string) => {
|
generatePrimitive = (schemaName: string) => {
|
||||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
|
||||||
entityFile.addStatements([
|
entityFile.addStatements([
|
||||||
'// This file was autogenerated. Please do not change.',
|
'// This file was autogenerated. Please do not change.',
|
||||||
'// All changes will be overwrited on commit.',
|
|
||||||
'',
|
'',
|
||||||
]);
|
]);
|
||||||
|
const { type: schemaType, description, pattern } = this.schemas[schemaName];
|
||||||
|
if (description) {
|
||||||
|
entityFile.addStatements(['\n/*', `Description: ${description}`, '*/\n']);
|
||||||
|
}
|
||||||
|
|
||||||
const { enum: enumMembers } = this.schemas[sName];
|
if (pattern) {
|
||||||
|
entityFile.addStatements(`const pattern = new RegExp('${pattern}')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const type: string = TYPES[schemaType as keyof typeof TYPES];
|
||||||
|
|
||||||
|
const entityClass = entityFile.addClass({
|
||||||
|
name: schemaName,
|
||||||
|
isDefaultExport: true,
|
||||||
|
extends: capitalize(type),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const ctor = entityClass.addConstructor({
|
||||||
|
parameters: [{
|
||||||
|
name: 'v',
|
||||||
|
type,
|
||||||
|
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
ctor.setBodyText((w) => {
|
||||||
|
const { minLength, minimum, maxLength, maximum } = this.schemas[schemaName];
|
||||||
|
|
||||||
|
if (type === 'string') {
|
||||||
|
if (pattern) {
|
||||||
|
w.writeLine('if (!v.match(pattern)) {');
|
||||||
|
w.writeLine(' throw new Error();');
|
||||||
|
w.writeLine('}');
|
||||||
|
}
|
||||||
|
if (typeof minLength === 'number') {
|
||||||
|
w.writeLine(`if (v.length < ${minLength}) {`);
|
||||||
|
w.writeLine(' throw new Error();');
|
||||||
|
w.writeLine('}');
|
||||||
|
}
|
||||||
|
if (typeof maxLength === 'number') {
|
||||||
|
w.writeLine(`if (v.length > ${maxLength}) {`);
|
||||||
|
w.writeLine(' throw new Error();');
|
||||||
|
w.writeLine('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'number') {
|
||||||
|
if (typeof minimum === 'number') {
|
||||||
|
w.writeLine(`if (v.length < ${minimum}) {`);
|
||||||
|
w.writeLine(' throw new Error();');
|
||||||
|
w.writeLine('}');
|
||||||
|
}
|
||||||
|
if (typeof maximum === 'number') {
|
||||||
|
w.writeLine(`if (v.length > ${maximum}) {`);
|
||||||
|
w.writeLine(' throw new Error();');
|
||||||
|
w.writeLine('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.writeLine('super(v);');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.entities.push(entityFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
generateEnum = (schemaName: string) => {
|
||||||
|
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
|
||||||
|
entityFile.addStatements([
|
||||||
|
'// This file was autogenerated. Please do not change.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
const { enum: enumMembers, description, example } = this.schemas[schemaName];
|
||||||
|
if (description) {
|
||||||
|
entityFile.addStatements(['\n/*', `Description: ${description}`, '*/\n']);
|
||||||
|
}
|
||||||
entityFile.addEnum({
|
entityFile.addEnum({
|
||||||
name: sName,
|
name: schemaName,
|
||||||
members: enumMembers.map((e: string) => ({ name: e.toUpperCase(), value: e })),
|
members: enumMembers!.map((e: string) => ({ name: e.toUpperCase(), value: e })),
|
||||||
isExported: true,
|
isExported: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.entities.push(entityFile);
|
this.entities.push(entityFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
generateOneOf = (sName: string) => {
|
generateOneOf = (schemaName: string) => {
|
||||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
|
||||||
entityFile.addStatements([
|
entityFile.addStatements([
|
||||||
'// This file was autogenerated. Please do not change.',
|
'// This file was autogenerated. Please do not change.',
|
||||||
'// All changes will be overwrited on commit.',
|
|
||||||
'',
|
'',
|
||||||
]);
|
]);
|
||||||
const importEntities: { type: string, isClass: boolean }[] = [];
|
const importEntities: { type: string, isClass: boolean }[] = [];
|
||||||
const entities = this.schemas[sName].oneOf.map((elem: any) => {
|
const entities = this.schemas[schemaName].oneOf.map((elem: any) => {
|
||||||
const [
|
const {
|
||||||
pType, isArray, isClass, isImport,
|
type: type, isArray, isClass, isImport,
|
||||||
] = schemaParamParser(elem, this.openapi);
|
} = schemaParamParser(elem, this.openapi);
|
||||||
importEntities.push({ type: pType, isClass });
|
importEntities.push({ type: type, isClass });
|
||||||
return { type: pType, isArray };
|
return { type: type, isArray };
|
||||||
});
|
});
|
||||||
entityFile.addTypeAlias({
|
entityFile.addTypeAlias({
|
||||||
name: sName,
|
name: schemaName,
|
||||||
isExported: true,
|
isExported: true,
|
||||||
type: entities.map((e: any) => e.isArray ? `I${e.type}[]` : `I${e.type}`).join(' | '),
|
type: entities.map((e: any) => e.isArray ? `I${e.type}[]` : `I${e.type}`).join(' | '),
|
||||||
})
|
})
|
||||||
|
|
||||||
// add import
|
// add import
|
||||||
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||||
const { type: pType, isClass } = ie;
|
const { type: type, isClass } = ie;
|
||||||
if (isClass) {
|
if (isClass) {
|
||||||
entityFile.addImportDeclaration({
|
entityFile.addImportDeclaration({
|
||||||
moduleSpecifier: `./${pType}`,
|
moduleSpecifier: `./${type}`,
|
||||||
namedImports: [`I${pType}`],
|
namedImports: [`I${type}`],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
entityFile.addImportDeclaration({
|
entityFile.addImportDeclaration({
|
||||||
moduleSpecifier: `./${pType}`,
|
moduleSpecifier: `./${type}`,
|
||||||
namedImports: [pType],
|
namedImports: [type],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.entities.push(entityFile);
|
this.entities.push(entityFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateClass = (sName: string) => {
|
generateClass = (schemaName: string) => {
|
||||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
|
||||||
entityFile.addStatements([
|
entityFile.addStatements([
|
||||||
'// This file was autogenerated. Please do not change.',
|
'// This file was autogenerated. Please do not change.',
|
||||||
'// All changes will be overwrited on commit.',
|
|
||||||
'',
|
'',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
let { properties, required, allOf, $ref } = this.schemas[schemaName];
|
||||||
|
|
||||||
|
if (allOf) {
|
||||||
|
const refLink: string = allOf.find((obj: Record<string, any>) => obj.$ref).$ref;
|
||||||
|
let ref: any = refLink.split('/')
|
||||||
|
ref = ref.pop();
|
||||||
|
|
||||||
|
const reasign = allOf.find((obj: Record<string, any>) => !obj.$ref);
|
||||||
|
const newSchema: Schema = { ...this.schemas[ref], ...reasign };
|
||||||
|
|
||||||
|
properties = newSchema.properties;
|
||||||
|
required = newSchema.required;
|
||||||
|
}
|
||||||
|
|
||||||
const { properties: sProps, required, $ref, additionalProperties } = this.schemas[sName];
|
|
||||||
if ($ref) {
|
if ($ref) {
|
||||||
const temp = $ref.split('/');
|
const refLink = $ref.split('/').pop()!;
|
||||||
const importSchemaName = `${temp[temp.length - 1]}`;
|
|
||||||
entityFile.addImportDeclaration({
|
entityFile.addImportDeclaration({
|
||||||
defaultImport: importSchemaName,
|
defaultImport: refLink,
|
||||||
moduleSpecifier: `./${importSchemaName}`,
|
moduleSpecifier: `./${refLink}`,
|
||||||
namedImports: [`I${importSchemaName}`],
|
namedImports: [`I${refLink}`],
|
||||||
});
|
});
|
||||||
|
|
||||||
entityFile.addTypeAlias({
|
entityFile.addTypeAlias({
|
||||||
name: `I${sName}`,
|
name: `I${schemaName}`,
|
||||||
type: `I${importSchemaName}`,
|
type: `I${refLink}`,
|
||||||
isExported: true,
|
isExported: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
entityFile.addStatements(`export default ${importSchemaName};`);
|
const entityClass = entityFile.addClass({
|
||||||
|
name: schemaName,
|
||||||
|
isDefaultExport: true,
|
||||||
|
extends: refLink,
|
||||||
|
})
|
||||||
|
const ctor = entityClass.addConstructor({
|
||||||
|
parameters: [{
|
||||||
|
name: 'props',
|
||||||
|
type: `I${schemaName}`,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
ctor.setBodyText((w) => {
|
||||||
|
w.writeLine('super(props);')
|
||||||
|
});
|
||||||
this.entities.push(entityFile);
|
this.entities.push(entityFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const importEntities: { type: string, isClass: boolean }[] = [];
|
|
||||||
const entityInterface = entityFile.addInterface({
|
const entityInterface = entityFile.addInterface({
|
||||||
name: `I${sName}`,
|
name: `I${schemaName}`,
|
||||||
isExported: true,
|
isExported: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedSProps = Object.keys(sProps || {}).sort();
|
const sortedProperties = Object.keys(properties || {}).sort();
|
||||||
const additionalPropsOnly = additionalProperties && sortedSProps.length === 0;
|
let importEntities: { type: string, isClass: boolean }[] = [];
|
||||||
|
|
||||||
|
type SortedPropertiesTypesValues = ReturnType<typeof schemaParamParser> & {
|
||||||
|
computedType: string;
|
||||||
|
isRequired: boolean;
|
||||||
|
}
|
||||||
|
const sortedPropertiesTypes = sortedProperties.reduce((data, propertyName) => {
|
||||||
|
const isRequired = !!(required && required.includes(propertyName));
|
||||||
|
const parsed = schemaParamParser(properties![propertyName], this.openapi);
|
||||||
|
data[propertyName] = {
|
||||||
|
...parsed,
|
||||||
|
isRequired,
|
||||||
|
computedType: `${parsed.type}${parsed.isArray ? '[]' : ''}${isRequired ? '' : ' | undefined'}`
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}, {} as Record<string, SortedPropertiesTypesValues>);
|
||||||
|
|
||||||
// add server response interface to entityFile
|
// add server response interface to entityFile
|
||||||
sortedSProps.forEach((sPropName) => {
|
sortedProperties.forEach((propertyName) => {
|
||||||
const [
|
const {
|
||||||
pType, isArray, isClass, isImport, isAdditional
|
type, isArray, isClass, isImport
|
||||||
] = schemaParamParser(sProps[sPropName], this.openapi);
|
} = sortedPropertiesTypes[propertyName];
|
||||||
|
|
||||||
if (isImport) {
|
if (isImport) {
|
||||||
importEntities.push({ type: pType, isClass });
|
importEntities.push({ type: type, isClass });
|
||||||
}
|
}
|
||||||
const propertyType = isAdditional
|
|
||||||
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
|
|
||||||
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
|
||||||
entityInterface.addProperty({
|
entityInterface.addProperty({
|
||||||
name: sPropName,
|
name: propertyName,
|
||||||
type: propertyType,
|
type: `${isClass ? 'I' : ''}${type}${isArray ? '[]' : ''}`,
|
||||||
hasQuestionToken: !(
|
hasQuestionToken: !(
|
||||||
(required && required.includes(sPropName)) || sProps[sPropName].required
|
(required && required.includes(propertyName)) || properties![propertyName].required
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (additionalProperties) {
|
|
||||||
const [
|
|
||||||
pType, isArray, isClass, isImport, isAdditional
|
|
||||||
] = schemaParamParser(additionalProperties, this.openapi);
|
|
||||||
|
|
||||||
if (isImport) {
|
|
||||||
importEntities.push({ type: pType, isClass });
|
|
||||||
}
|
|
||||||
const type = isAdditional
|
|
||||||
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
|
|
||||||
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
|
||||||
entityInterface.addIndexSignature({
|
|
||||||
keyName: 'key',
|
|
||||||
keyType: 'string',
|
|
||||||
returnType: additionalPropsOnly ? type : `${type} | undefined`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add import
|
// add import
|
||||||
const imports: { type: string, isClass: boolean }[] = [];
|
|
||||||
const types: string[] = [];
|
const types: string[] = [];
|
||||||
importEntities.forEach((i) => {
|
importEntities = importEntities.filter((i) => {
|
||||||
const { type } = i;
|
const { type } = i;
|
||||||
if (!types.includes(type)) {
|
if (!types.includes(type)) {
|
||||||
imports.push(i);
|
|
||||||
types.push(type);
|
types.push(type);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
imports.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||||
const { type: pType, isClass } = ie;
|
const { type: type, isClass } = ie;
|
||||||
if (isClass) {
|
if (isClass) {
|
||||||
entityFile.addImportDeclaration({
|
entityFile.addImportDeclaration({
|
||||||
defaultImport: pType,
|
defaultImport: type,
|
||||||
moduleSpecifier: `./${pType}`,
|
moduleSpecifier: `./${type}`,
|
||||||
namedImports: [`I${pType}`],
|
namedImports: [`I${type}`],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
entityFile.addImportDeclaration({
|
entityFile.addImportDeclaration({
|
||||||
moduleSpecifier: `./${pType}`,
|
moduleSpecifier: `./${type}`,
|
||||||
namedImports: [pType],
|
namedImports: [type],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const entityClass = entityFile.addClass({
|
const entityClass = entityFile.addClass({
|
||||||
name: sName,
|
name: schemaName,
|
||||||
isDefaultExport: true,
|
isDefaultExport: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// addProperties to class;
|
// addProperties to class;
|
||||||
sortedSProps.forEach((sPropName) => {
|
sortedProperties.forEach((propertyName) => {
|
||||||
const [pType, isArray, isClass, isImport, isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
const { type, isArray, isClass, isEnum, isRequired, computedType } = sortedPropertiesTypes[propertyName];
|
||||||
|
|
||||||
const isRequred = (required && required.includes(sPropName))
|
|
||||||
|| sProps[sPropName].required;
|
|
||||||
|
|
||||||
const propertyType = isAdditional
|
|
||||||
? `{ [key: string]: ${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'} }`
|
|
||||||
: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`;
|
|
||||||
|
|
||||||
entityClass.addProperty({
|
entityClass.addProperty({
|
||||||
name: `_${sPropName}`,
|
name: `_${propertyName}`,
|
||||||
isReadonly: true,
|
isReadonly: true,
|
||||||
type: propertyType,
|
type: computedType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getter = entityClass.addGetAccessor({
|
const getter = entityClass.addGetAccessor({
|
||||||
name: toCamel(sPropName),
|
name: toCamel(propertyName),
|
||||||
returnType: propertyType,
|
returnType: computedType,
|
||||||
statements: [`return this._${sPropName};`],
|
statements: [`return this._${propertyName};`],
|
||||||
});
|
});
|
||||||
const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = properties![propertyName];
|
||||||
|
|
||||||
if (description || example) {
|
if (description || example) {
|
||||||
getter.addJsDoc(`${example ? `Description: ${description}` : ''}${example ? `\nExample: ${example}` : ''}`);
|
getter.addJsDoc(`${example ? `Description: ${description}` : ''}${example ? `\nExample: ${example}` : ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minItems) {
|
if (minItems) {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MinItems`,
|
isReadonly: true,
|
||||||
statements: [`return ${minItems};`],
|
name: `${capitalize(toCamel(propertyName))}MinItems`,
|
||||||
|
initializer: `${minItems}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maxItems) {
|
if (maxItems) {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MaxItems`,
|
isReadonly: true,
|
||||||
statements: [`return ${maxItems};`],
|
name: `${capitalize(toCamel(propertyName))}MaxItems`,
|
||||||
|
initializer: `${maxItems}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (typeof minLength === 'number') {
|
if (typeof minLength === 'number') {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MinLength`,
|
isReadonly: true,
|
||||||
statements: [`return ${minLength};`],
|
name: `${capitalize(toCamel(propertyName))}MinLength`,
|
||||||
|
initializer: `${minLength}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maxLength) {
|
if (maxLength) {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MaxLength`,
|
isReadonly: true,
|
||||||
statements: [`return ${maxLength};`],
|
name: `${capitalize(toCamel(propertyName))}MaxLength`,
|
||||||
|
initializer: `${maxLength}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (typeof minimum === 'number') {
|
if (typeof minimum === 'number') {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MinValue`,
|
isReadonly: true,
|
||||||
statements: [`return ${minimum};`],
|
name: `${capitalize(toCamel(propertyName))}MinValue`,
|
||||||
|
initializer: `${minimum}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maximum) {
|
if (maximum) {
|
||||||
entityClass.addGetAccessor({
|
entityClass.addProperty({
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
name: `${toCamel(sPropName)}MaxValue`,
|
isReadonly: true,
|
||||||
statements: [`return ${maximum};`],
|
name: `${capitalize(toCamel(propertyName))}MaxValue`,
|
||||||
|
initializer: `${maximum}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(isArray && isClass) && !isClass) {
|
|
||||||
const isEnum = !isClass && isImport;
|
|
||||||
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
|
|
||||||
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
|
||||||
const haveValidationFields = maxLength || typeof minLength === 'number' || maximum || typeof minimum === 'number';
|
|
||||||
if (isRequired || haveValidationFields) {
|
|
||||||
const prop = toCamel(sPropName);
|
|
||||||
const validateField = entityClass.addMethod({
|
|
||||||
isStatic: true,
|
|
||||||
name: `${prop}Validate`,
|
|
||||||
returnType: `boolean`,
|
|
||||||
parameters: [{
|
|
||||||
name: prop,
|
|
||||||
type: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`,
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
|
|
||||||
validateField.setBodyText((w) => {
|
|
||||||
w.write('return ');
|
|
||||||
const nonRequiredCall = isRequired ? prop : `!${prop} ? true : ${prop}`;
|
|
||||||
if (pType === 'string') {
|
|
||||||
if (isArray) {
|
|
||||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && (typeof p === 'string' && !!p.trim()), true)`);
|
|
||||||
} else {
|
|
||||||
if (typeof minLength === 'number' && maxLength) {
|
|
||||||
w.write(`(${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength})`);
|
|
||||||
}
|
|
||||||
if (typeof minLength !== 'number' || !maxLength) {
|
|
||||||
w.write(`${isRequired ? `typeof ${prop} === 'string'` : `!${prop} ? true : typeof ${prop} === 'string'`} && !!${nonRequiredCall}.trim()`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (pType === 'number') {
|
|
||||||
if (isArray) {
|
|
||||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'number', true)`);
|
|
||||||
} else {
|
|
||||||
if (typeof minimum === 'number' && maximum) {
|
|
||||||
w.write(`${isRequired ? `${prop} >= ${minimum} && ${prop} <= ${maximum}` : `!${prop} ? true : ((${prop} >= ${minimum}) && (${prop} <= ${maximum}))`}`);
|
|
||||||
}
|
|
||||||
if (typeof minimum !== 'number' || !maximum) {
|
|
||||||
w.write(`${isRequired ? `typeof ${prop} === 'number'` : `!${prop} ? true : typeof ${prop} === 'number'`}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (pType === 'boolean') {
|
|
||||||
w.write(`${isRequired ? `typeof ${prop} === 'boolean'` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
|
|
||||||
} else if (isEnum) {
|
|
||||||
if (isArray){
|
|
||||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && Object.keys(${pType}).includes(${prop}), true)`);
|
|
||||||
} else {
|
|
||||||
w.write(`${isRequired ? `Object.keys(${pType}).includes(${prop})` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.write(';');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (additionalProperties) {
|
|
||||||
const [
|
|
||||||
pType, isArray, isClass, isImport, isAdditional
|
|
||||||
] = schemaParamParser(additionalProperties, this.openapi);
|
|
||||||
const type = `Record<string, ${pType}${isArray ? '[]' : ''}>`;
|
|
||||||
|
|
||||||
entityClass.addProperty({
|
|
||||||
name: additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`,
|
|
||||||
isReadonly: true,
|
|
||||||
type: type,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// add constructor;
|
// add constructor;
|
||||||
const ctor = entityClass.addConstructor({
|
const ctor = entityClass.addConstructor({
|
||||||
parameters: [{
|
parameters: [{
|
||||||
name: 'props',
|
name: 'props',
|
||||||
type: `I${sName}`,
|
type: `I${schemaName}`,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
ctor.setBodyText((w) => {
|
ctor.setBodyText((w) => {
|
||||||
if (additionalProperties) {
|
sortedProperties.forEach((propertyName) => {
|
||||||
const [
|
const { type, isArray, isClass, isRequired } = sortedPropertiesTypes[propertyName];
|
||||||
pType, isArray, isClass, isImport, isAdditional
|
|
||||||
] = schemaParamParser(additionalProperties, this.openapi);
|
const indent = !isRequired ? ' ' : '';
|
||||||
w.writeLine(`this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`} = Object.entries(props).reduce<Record<string, ${pType}>>((prev, [key, value]) => {`);
|
if (!isRequired) {
|
||||||
if (isClass) {
|
if ((type === 'boolean' || type === 'number' || type ==='string') && !isClass && !isArray) {
|
||||||
w.writeLine(` prev[key] = new ${pType}(value!);`);
|
w.writeLine(`if (typeof props.${propertyName} === '${type}') {`);
|
||||||
|
} else {
|
||||||
|
w.writeLine(`if (props.${propertyName}) {`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isArray && isClass) {
|
||||||
|
w.writeLine(`${indent}this._${propertyName} = props.${propertyName}.map((p) => new ${type}(p));`);
|
||||||
|
} else if (isClass) {
|
||||||
|
w.writeLine(`${indent}this._${propertyName} = new ${type}(props.${propertyName});`);
|
||||||
} else {
|
} else {
|
||||||
w.writeLine(' prev[key] = value!;')
|
if (type === 'string' && !isArray) {
|
||||||
}
|
w.writeLine(`${indent}this._${propertyName} = props.${propertyName}.trim();`);
|
||||||
w.writeLine(' return prev;');
|
|
||||||
w.writeLine('}, {})');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sortedSProps.forEach((sPropName) => {
|
|
||||||
const [
|
|
||||||
pType, isArray, isClass, , isAdditional
|
|
||||||
] = schemaParamParser(sProps[sPropName], this.openapi);
|
|
||||||
const req = (required && required.includes(sPropName))
|
|
||||||
|| sProps[sPropName].required;
|
|
||||||
if (!req) {
|
|
||||||
if ((pType === 'boolean' || pType === 'number' || pType ==='string') && !isClass && !isArray) {
|
|
||||||
w.writeLine(`if (typeof props.${sPropName} === '${pType}') {`);
|
|
||||||
} else {
|
} else {
|
||||||
w.writeLine(`if (props.${sPropName}) {`);
|
w.writeLine(`${indent}this._${propertyName} = props.${propertyName};`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAdditional) {
|
if (!isRequired) {
|
||||||
if (isArray && isClass) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => {
|
|
||||||
return { ...prev, [key]: new ${pType}(p[key])};
|
|
||||||
},{}))`);
|
|
||||||
} else if (isClass) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
|
||||||
return { ...prev, [key]: new ${pType}(props.${sPropName}[key])};
|
|
||||||
},{})`);
|
|
||||||
} else {
|
|
||||||
if (pType === 'string' && !isArray) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
|
||||||
return { ...prev, [key]: props.${sPropName}[key].trim()};
|
|
||||||
},{})`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
|
||||||
return { ...prev, [key]: props.${sPropName}[key]};
|
|
||||||
},{})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isArray && isClass) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => new ${pType}(p));`);
|
|
||||||
} else if (isClass) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = new ${pType}(props.${sPropName});`);
|
|
||||||
} else {
|
|
||||||
if (pType === 'string' && !isArray) {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.trim();`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName};`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!req) {
|
|
||||||
w.writeLine('}');
|
w.writeLine('}');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// add serialize method;
|
// add serialize method;
|
||||||
const serialize = entityClass.addMethod({
|
const serialize = entityClass.addMethod({
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
name: 'serialize',
|
name: 'serialize',
|
||||||
returnType: `I${sName}`,
|
returnType: `I${schemaName}`,
|
||||||
});
|
});
|
||||||
serialize.setBodyText((w) => {
|
serialize.setBodyText((w) => {
|
||||||
if (additionalProperties) {
|
w.writeLine(`const data: I${schemaName} = {`);
|
||||||
const [
|
|
||||||
pType, isArray, isClass, isImport, isAdditional
|
|
||||||
] = schemaParamParser(additionalProperties, this.openapi);
|
|
||||||
w.writeLine(`return Object.entries(this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`}).reduce<Record<string, ${isClass ? 'I' : ''}${pType}>>((prev, [key, value]) => {`);
|
|
||||||
if (isClass) {
|
|
||||||
w.writeLine(` prev[key] = value.serialize();`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(' prev[key] = value;')
|
|
||||||
}
|
|
||||||
w.writeLine(' return prev;');
|
|
||||||
w.writeLine('}, {})');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
w.writeLine(`const data: I${sName} = {`);
|
|
||||||
const unReqFields: string[] = [];
|
const unReqFields: string[] = [];
|
||||||
sortedSProps.forEach((sPropName) => {
|
|
||||||
const req = (required && required.includes(sPropName))
|
sortedProperties.forEach((propertyName) => {
|
||||||
|| sProps[sPropName].required;
|
const {isArray, isClass, isRequired } = sortedPropertiesTypes[propertyName];
|
||||||
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
if (!isRequired) {
|
||||||
if (!req) {
|
unReqFields.push(propertyName);
|
||||||
unReqFields.push(sPropName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isAdditional) {
|
if (isArray && isClass) {
|
||||||
if (isArray && isClass) {
|
w.writeLine(` ${propertyName}: this._${propertyName}.map((p) => p.serialize()),`);
|
||||||
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }))),`);
|
} else if (isClass) {
|
||||||
} else if (isClass) {
|
w.writeLine(` ${propertyName}: this._${propertyName}.serialize(),`);
|
||||||
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce<Record<string, any>>((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {}),`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] })),`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isArray && isClass) {
|
w.writeLine(` ${propertyName}: this._${propertyName},`);
|
||||||
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => p.serialize()),`);
|
|
||||||
} else if (isClass) {
|
|
||||||
w.writeLine(` ${sPropName}: this._${sPropName}.serialize(),`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(` ${sPropName}: this._${sPropName},`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
w.writeLine('};');
|
w.writeLine('};');
|
||||||
unReqFields.forEach((sPropName) => {
|
|
||||||
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
unReqFields.forEach((propertyName) => {
|
||||||
w.writeLine(`if (typeof this._${sPropName} !== 'undefined') {`);
|
const { isArray, isClass } = sortedPropertiesTypes[propertyName];
|
||||||
if (isAdditional) {
|
w.writeLine(`if (typeof this._${propertyName} !== 'undefined') {`);
|
||||||
if (isArray && isClass) {
|
if (isArray && isClass) {
|
||||||
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }), {}));`);
|
w.writeLine(` data.${propertyName} = this._${propertyName}.map((p) => p.serialize());`);
|
||||||
} else if (isClass) {
|
} else if (isClass) {
|
||||||
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {});`);
|
w.writeLine(` data.${propertyName} = this._${propertyName}.serialize();`);
|
||||||
} else {
|
|
||||||
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] }), {});`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isArray && isClass) {
|
w.writeLine(` data.${propertyName} = this._${propertyName};`);
|
||||||
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => p.serialize());`);
|
|
||||||
} else if (isClass) {
|
|
||||||
w.writeLine(` data.${sPropName} = this._${sPropName}.serialize();`);
|
|
||||||
} else {
|
|
||||||
w.writeLine(` data.${sPropName} = this._${sPropName};`);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.writeLine(`}`);
|
w.writeLine(`}`);
|
||||||
});
|
});
|
||||||
w.writeLine('return data;');
|
w.writeLine('return data;');
|
||||||
@@ -522,74 +510,55 @@ class EntitiesGenerator {
|
|||||||
returnType: `string[]`,
|
returnType: `string[]`,
|
||||||
})
|
})
|
||||||
validate.setBodyText((w) => {
|
validate.setBodyText((w) => {
|
||||||
if (additionalPropsOnly) {
|
w.writeLine('const validateRequired = {');
|
||||||
w.writeLine('return []')
|
Object.keys(properties || {}).forEach((propertyName) => {
|
||||||
return;
|
const { isArray, isClass, type, isRequired } = sortedPropertiesTypes[propertyName];
|
||||||
}
|
const { maxLength, minLength, maximum, minimum } = properties![propertyName];
|
||||||
w.writeLine('const validate = {');
|
|
||||||
Object.keys(sProps || {}).forEach((sPropName) => {
|
|
||||||
const [pType, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
|
||||||
|
|
||||||
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
const nonRequiredCall = isRequired ? `this._${propertyName}` : `!this._${propertyName} ? true : this._${propertyName}`;
|
||||||
|
|
||||||
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
|
|
||||||
const nonRequiredCall = isRequired ? `this._${sPropName}` : `!this._${sPropName} ? true : this._${sPropName}`;
|
|
||||||
|
|
||||||
if (isArray && isClass) {
|
if (isArray && isClass) {
|
||||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && p.validate().length === 0, true),`);
|
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && p.validate().length === 0, true),`);
|
||||||
} else if (isClass && !isAdditional) {
|
} else if (isClass) {
|
||||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.validate().length === 0,`);
|
w.writeLine(` ${propertyName}: ${nonRequiredCall}.validate().length === 0,`);
|
||||||
} else {
|
} else {
|
||||||
if (pType === 'string') {
|
if (type === 'string') {
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'string', true),`);
|
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'string', true),`);
|
||||||
} else {
|
} else {
|
||||||
if (typeof minLength === 'number' && maxLength) {
|
if (typeof minLength === 'number' && maxLength) {
|
||||||
w.writeLine(` ${sPropName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`);
|
w.writeLine(` ${propertyName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`);
|
||||||
}
|
}
|
||||||
if (typeof minLength !== 'number' || !maxLength) {
|
if (typeof minLength !== 'number' || !maxLength) {
|
||||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'string'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'string'`} && !this._${sPropName} ? true : this._${sPropName},`);
|
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'string' && !!this._${propertyName}.trim()` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'string'`},`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (pType === 'number') {
|
} else if (type === 'number') {
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'number', true),`);
|
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'number', true),`);
|
||||||
} else {
|
} else {
|
||||||
if (typeof minimum === 'number' && maximum) {
|
if (typeof minimum === 'number' && maximum) {
|
||||||
w.writeLine(` ${sPropName}: ${isRequired ? `this._${sPropName} >= ${minimum} && this._${sPropName} <= ${maximum}` : `!this._${sPropName} ? true : ((this._${sPropName} >= ${minimum}) && (this._${sPropName} <= ${maximum}))`},`);
|
w.writeLine(` ${propertyName}: ${isRequired ? `this._${propertyName} >= ${minimum} && this._${propertyName} <= ${maximum}` : `!this._${propertyName} ? true : ((this._${propertyName} >= ${minimum}) && (this._${propertyName} <= ${maximum}))`},`);
|
||||||
}
|
}
|
||||||
if (typeof minimum !== 'number' || !maximum) {
|
if (typeof minimum !== 'number' || !maximum) {
|
||||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'number'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'number'`},`);
|
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'number'` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'number'`},`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (pType === 'boolean') {
|
} else if (type === 'boolean') {
|
||||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'boolean'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'boolean'`},`);
|
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'boolean'` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'boolean'`},`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
w.writeLine('};');
|
w.writeLine('};');
|
||||||
w.writeLine('const isError: string[] = [];')
|
w.writeLine('const errorInFields: string[] = [];')
|
||||||
w.writeLine('Object.keys(validate).forEach((key) => {');
|
w.writeLine('Object.keys(validateRequired).forEach((key) => {');
|
||||||
w.writeLine(' if (!(validate as any)[key]) {');
|
w.writeLine(' if (!(validateRequired as any)[key]) {');
|
||||||
w.writeLine(' isError.push(key);');
|
w.writeLine(' errorInFields.push(key);');
|
||||||
w.writeLine(' }');
|
w.writeLine(' }');
|
||||||
w.writeLine('});');
|
w.writeLine('});');
|
||||||
w.writeLine('return isError;');
|
w.writeLine('return errorInFields;');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// add update method;
|
|
||||||
const update = entityClass.addMethod({
|
|
||||||
isStatic: false,
|
|
||||||
name: 'update',
|
|
||||||
returnType: `${sName}`,
|
|
||||||
});
|
|
||||||
update.addParameter({
|
|
||||||
name: 'props',
|
|
||||||
type: additionalPropsOnly ? `I${sName}` : `Partial<I${sName}>`,
|
|
||||||
});
|
|
||||||
update.setBodyText((w) => { w.writeLine(`return new ${sName}({ ...this.serialize(), ...props });`); });
|
|
||||||
|
|
||||||
this.entities.push(entityFile);
|
this.entities.push(entityFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,65 +19,154 @@ const TYPES = {
|
|||||||
boolean: 'boolean',
|
boolean: 'boolean',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum SchemaType {
|
||||||
|
STRING = 'string',
|
||||||
|
OBJECT = 'object',
|
||||||
|
ARRAY = 'array',
|
||||||
|
BOOLEAN = 'boolean',
|
||||||
|
NUMBER = 'number',
|
||||||
|
INTEGER = 'integer',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Schema {
|
||||||
|
allOf?: any[];
|
||||||
|
example?: string;
|
||||||
|
properties?: Record<string, Schema>;
|
||||||
|
required?: string[];
|
||||||
|
description?: string;
|
||||||
|
enum?: string[];
|
||||||
|
type: SchemaType;
|
||||||
|
pattern?: string;
|
||||||
|
oneOf?: any
|
||||||
|
items?: Schema;
|
||||||
|
additionalProperties?: Schema;
|
||||||
|
$ref?: string;
|
||||||
|
minItems?: number;
|
||||||
|
maxItems?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
minLength?: number;
|
||||||
|
maximum?: number;
|
||||||
|
minimum?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Parameter {
|
||||||
|
description?: string;
|
||||||
|
example?: string;
|
||||||
|
in?: 'query' | 'body' | 'headers';
|
||||||
|
name: string;
|
||||||
|
schema: Schema;
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequestBody {
|
||||||
|
content: {
|
||||||
|
'application/json'?: {
|
||||||
|
schema: Schema;
|
||||||
|
example?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
export interface Response {
|
||||||
|
content: {
|
||||||
|
'application/json'?: {
|
||||||
|
schema: Schema;
|
||||||
|
example?: string;
|
||||||
|
};
|
||||||
|
'text/palin'?: {
|
||||||
|
example?: string;
|
||||||
|
'x-error-class'?: string;
|
||||||
|
'x-error-code'?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Schemas {
|
||||||
|
parameters: Record<string, Parameter>;
|
||||||
|
requestBodies: Record<string, RequestBody>;
|
||||||
|
responses: Record<string, Response>;
|
||||||
|
schemas: Record<string, Schema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenApi {
|
||||||
|
components: Schemas;
|
||||||
|
paths: any;
|
||||||
|
servers: {
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param schemaProp: valueof shema.properties[key]
|
* @param schemaProp: valueof shema.properties[key]
|
||||||
* @param openApi: openapi object
|
* @param openApi: openapi object
|
||||||
* @returns [propType - basicType or import one, isArray, isClass, isImport]
|
* @returns [propType - basicType or import one, isArray, isClass, isImport]
|
||||||
*/
|
*/
|
||||||
const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boolean, boolean, boolean] => {
|
interface SchemaParamParserReturn {
|
||||||
|
type: string;
|
||||||
|
isArray: boolean;
|
||||||
|
isClass: boolean;
|
||||||
|
isImport: boolean;
|
||||||
|
isAdditional: boolean;
|
||||||
|
isEnum: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaParamParser = (schemaProp: Schema, openApi: OpenApi): SchemaParamParserReturn => {
|
||||||
let type = '';
|
let type = '';
|
||||||
let isImport = false;
|
let isImport = false;
|
||||||
let isClass = false;
|
let isClass = false;
|
||||||
let isArray = false;
|
let isArray = false;
|
||||||
let isAdditional = false;
|
let isAdditional = false;
|
||||||
|
let isEnum = false;
|
||||||
|
|
||||||
if (schemaProp.$ref || schemaProp.additionalProperties?.$ref) {
|
if (schemaProp.$ref || schemaProp.additionalProperties?.$ref) {
|
||||||
const temp = (schemaProp.$ref || schemaProp.additionalProperties?.$ref).split('/');
|
type = (schemaProp.$ref || schemaProp.additionalProperties?.$ref)!.split('/').pop()!;
|
||||||
|
|
||||||
if (schemaProp.additionalProperties) {
|
if (schemaProp.additionalProperties) {
|
||||||
isAdditional = true;
|
isAdditional = true;
|
||||||
}
|
}
|
||||||
|
const cl = openApi.components.schemas[type];
|
||||||
type = `${temp[temp.length - 1]}`;
|
|
||||||
|
if (cl.allOf) {
|
||||||
const cl = openApi ? openApi.components.schemas[type] : {};
|
const ref = cl.allOf.find((e) => !!e.$ref);
|
||||||
|
const link = schemaParamParser(ref, openApi);
|
||||||
|
return {...link, type};
|
||||||
|
}
|
||||||
|
|
||||||
if (cl.$ref) {
|
if (cl.$ref) {
|
||||||
const link = schemaParamParser(cl, openApi);
|
const link = schemaParamParser(cl, openApi);
|
||||||
link.shift();
|
return {...link, type};
|
||||||
return [type, ...link] as any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cl.type === 'string' && cl.enum) {
|
if (cl.type === 'string' && cl.enum) {
|
||||||
isImport = true;
|
isImport = true;
|
||||||
|
isEnum = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cl.type === 'object' && !cl.oneOf) {
|
if (cl.type === 'object' && !cl.oneOf) {
|
||||||
isClass = true;
|
isClass = true;
|
||||||
isImport = true;
|
isImport = true;
|
||||||
} else if (cl.type === 'array') {
|
} else if (cl.type === 'array') {
|
||||||
const temp: any = schemaParamParser(cl.items, openApi);
|
const temp = schemaParamParser(cl.items!, openApi);
|
||||||
type = `${temp[0]}`;
|
type = temp.type;
|
||||||
isArray = true;
|
isArray = true;
|
||||||
isClass = isClass || temp[2];
|
isClass = isClass || temp.isClass;
|
||||||
isImport = isImport || temp[3];
|
isImport = isImport || temp.isImport;
|
||||||
|
isEnum = isEnum || temp.isEnum;
|
||||||
}
|
}
|
||||||
} else if (schemaProp.type === 'array') {
|
} else if (schemaProp.type === 'array') {
|
||||||
const temp: any = schemaParamParser(schemaProp.items, openApi);
|
const temp = schemaParamParser(schemaProp.items!, openApi);
|
||||||
type = `${temp[0]}`;
|
type = temp.type
|
||||||
isArray = true;
|
isArray = true;
|
||||||
isClass = isClass || temp[2];
|
isClass = isClass || temp.isClass;
|
||||||
isImport = isImport || temp[3];
|
isImport = isImport || temp.isImport;
|
||||||
|
isEnum = isEnum || temp.isEnum;
|
||||||
} else {
|
} else {
|
||||||
type = (TYPES as Record<any, string>)[schemaProp.type];
|
type = (TYPES as Record<any, string>)[schemaProp.type];
|
||||||
}
|
}
|
||||||
if (!type) {
|
|
||||||
// TODO: Fix bug with Error fields.
|
|
||||||
type = 'any';
|
|
||||||
// throw new Error('Failed to find entity type');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [type, isArray, isClass, isImport, isAdditional];
|
return { type, isArray, isClass, isImport, isAdditional, isEnum };
|
||||||
};
|
};
|
||||||
|
|
||||||
export { TYPES, toCamel, capitalize, uncapitalize, schemaParamParser };
|
export { TYPES, toCamel, capitalize, uncapitalize, schemaParamParser };
|
||||||
|
|||||||
@@ -370,17 +370,16 @@
|
|||||||
remark "^13.0.0"
|
remark "^13.0.0"
|
||||||
unist-util-find-all-after "^3.0.2"
|
unist-util-find-all-after "^3.0.2"
|
||||||
|
|
||||||
"@ts-morph/common@~0.6.0":
|
"@ts-morph/common@~0.8.0":
|
||||||
version "0.6.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.6.0.tgz#cbd4ee57c5ef971511b9c5778e0bb8eb27de4783"
|
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.8.0.tgz#ae7b292df8258040465c50b378108ec8f09a9516"
|
||||||
integrity sha512-pI35nZz5bs3tL3btSVX2cWkAE8rc80F+Fn4TwSC6bQvn7fgn9IyLXVcAfpG6X6NBY5wN9TkSWXn/QYUkBvR/Fw==
|
integrity sha512-YbjWiMXLMKxWxcMqP47nwZVWVBwoF5B65dtRz0lya2LetjldAPxTxRbRo1n4Iszr2tSvzXeaa+f1AbULmfc5uA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@dsherret/to-absolute-glob" "^2.0.2"
|
"@dsherret/to-absolute-glob" "^2.0.2"
|
||||||
fast-glob "^3.2.4"
|
fast-glob "^3.2.5"
|
||||||
fs-extra "^9.0.1"
|
|
||||||
is-negated-glob "^1.0.0"
|
is-negated-glob "^1.0.0"
|
||||||
multimatch "^4.0.0"
|
mkdirp "^1.0.4"
|
||||||
typescript "~4.0.2"
|
multimatch "^5.0.0"
|
||||||
|
|
||||||
"@types/anymatch@*":
|
"@types/anymatch@*":
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
@@ -1172,11 +1171,6 @@ async@^2.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.14"
|
lodash "^4.17.14"
|
||||||
|
|
||||||
at-least-node@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
|
||||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
|
||||||
|
|
||||||
atob@^2.1.2:
|
atob@^2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
@@ -1593,7 +1587,7 @@ coa@^2.0.2:
|
|||||||
chalk "^2.4.1"
|
chalk "^2.4.1"
|
||||||
q "^1.1.2"
|
q "^1.1.2"
|
||||||
|
|
||||||
code-block-writer@^10.1.0:
|
code-block-writer@^10.1.1:
|
||||||
version "10.1.1"
|
version "10.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f"
|
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f"
|
||||||
integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==
|
integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==
|
||||||
@@ -2910,9 +2904,9 @@ fastest-levenshtein@^1.0.12:
|
|||||||
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
|
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
|
||||||
|
|
||||||
fastq@^1.6.0:
|
fastq@^1.6.0:
|
||||||
version "1.10.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.0.tgz#74dbefccade964932cdf500473ef302719c652bb"
|
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858"
|
||||||
integrity sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==
|
integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify "^1.0.4"
|
reusify "^1.0.4"
|
||||||
|
|
||||||
@@ -3084,16 +3078,6 @@ fs-extra@^8.1.0:
|
|||||||
jsonfile "^4.0.0"
|
jsonfile "^4.0.0"
|
||||||
universalify "^0.1.0"
|
universalify "^0.1.0"
|
||||||
|
|
||||||
fs-extra@^9.0.1:
|
|
||||||
version "9.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
|
||||||
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
|
||||||
dependencies:
|
|
||||||
at-least-node "^1.0.0"
|
|
||||||
graceful-fs "^4.2.0"
|
|
||||||
jsonfile "^6.0.1"
|
|
||||||
universalify "^2.0.0"
|
|
||||||
|
|
||||||
fs-minipass@^2.0.0:
|
fs-minipass@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||||
@@ -3267,11 +3251,16 @@ gonzales-pe@^4.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.4:
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||||
|
|
||||||
|
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||||
|
version "4.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
|
||||||
|
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
||||||
|
|
||||||
handle-thing@^2.0.0:
|
handle-thing@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
||||||
@@ -4145,15 +4134,6 @@ jsonfile@^4.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsonfile@^6.0.1:
|
|
||||||
version "6.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
|
||||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
|
||||||
dependencies:
|
|
||||||
universalify "^2.0.0"
|
|
||||||
optionalDependencies:
|
|
||||||
graceful-fs "^4.1.6"
|
|
||||||
|
|
||||||
"jsx-ast-utils@^2.4.1 || ^3.0.0":
|
"jsx-ast-utils@^2.4.1 || ^3.0.0":
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
|
||||||
@@ -4784,10 +4764,10 @@ multicast-dns@^6.0.1:
|
|||||||
dns-packet "^1.3.1"
|
dns-packet "^1.3.1"
|
||||||
thunky "^1.0.2"
|
thunky "^1.0.2"
|
||||||
|
|
||||||
multimatch@^4.0.0:
|
multimatch@^5.0.0:
|
||||||
version "4.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3"
|
resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6"
|
||||||
integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==
|
integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/minimatch" "^3.0.3"
|
"@types/minimatch" "^3.0.3"
|
||||||
array-differ "^3.0.0"
|
array-differ "^3.0.0"
|
||||||
@@ -6292,6 +6272,11 @@ querystringify@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||||
|
|
||||||
|
queue-microtask@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3"
|
||||||
|
integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==
|
||||||
|
|
||||||
quick-lru@^4.0.1:
|
quick-lru@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
|
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
|
||||||
@@ -7086,9 +7071,11 @@ rimraf@^3.0.2:
|
|||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
run-parallel@^1.1.9:
|
run-parallel@^1.1.9:
|
||||||
version "1.1.10"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
|
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||||
integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
|
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||||
|
dependencies:
|
||||||
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
@@ -7942,14 +7929,14 @@ ts-loader@^8.0.6:
|
|||||||
micromatch "^4.0.0"
|
micromatch "^4.0.0"
|
||||||
semver "^7.3.4"
|
semver "^7.3.4"
|
||||||
|
|
||||||
ts-morph@^8.1.2:
|
ts-morph@^10.0.1:
|
||||||
version "8.2.0"
|
version "10.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-8.2.0.tgz#41d83cd501cbd897eb029ac489d6d5b927555c57"
|
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-10.0.1.tgz#5a620cc4ef85e3e6d161989e690f44d0a0f723b0"
|
||||||
integrity sha512-NHHWu+7I2/AOZiTni5w3f+xCfIxrkzPCcQbTGa81Yk3pr23a4h9xLLEE6tIGuYIubWjkjr9QVC3ITqgmA5touQ==
|
integrity sha512-T1zufImtp5goTLTFhzi7XuKR1y/f+Jwz1gSULzB045LFjXuoqVlR87sfkpyWow8u2JwgusCJrhOnwmHCFNutTQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@dsherret/to-absolute-glob" "^2.0.2"
|
"@dsherret/to-absolute-glob" "^2.0.2"
|
||||||
"@ts-morph/common" "~0.6.0"
|
"@ts-morph/common" "~0.8.0"
|
||||||
code-block-writer "^10.1.0"
|
code-block-writer "^10.1.1"
|
||||||
|
|
||||||
ts-node@^9.0.0:
|
ts-node@^9.0.0:
|
||||||
version "9.1.1"
|
version "9.1.1"
|
||||||
@@ -8032,11 +8019,6 @@ typescript@^4.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
||||||
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
||||||
|
|
||||||
typescript@~4.0.2:
|
|
||||||
version "4.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389"
|
|
||||||
integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==
|
|
||||||
|
|
||||||
unc-path-regex@^0.1.2:
|
unc-path-regex@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||||
@@ -8112,11 +8094,6 @@ universalify@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
universalify@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
|
||||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
|
||||||
|
|
||||||
unpipe@1.0.0, unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.33.9
|
github.com/AdguardTeam/dnsproxy v0.33.9
|
||||||
github.com/AdguardTeam/golibs v0.4.4
|
github.com/AdguardTeam/golibs v0.4.4
|
||||||
github.com/AdguardTeam/urlfilter v0.14.2
|
github.com/AdguardTeam/urlfilter v0.14.3
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.0.1
|
github.com/ameshkov/dnscrypt/v2 v2.0.1
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
|
|||||||
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||||
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||||
github.com/AdguardTeam/urlfilter v0.14.2 h1:k26vEYz0mT/liDGZ0JGIBLYLMHaisIGX1UR0qaVnO4k=
|
github.com/AdguardTeam/urlfilter v0.14.3 h1:MBaLEXS0LRQNbHtLkDCYhHINDPtkevPrYWGiOUuLJU4=
|
||||||
github.com/AdguardTeam/urlfilter v0.14.2/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
github.com/AdguardTeam/urlfilter v0.14.3/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLimitReadCloser(t *testing.T) {
|
func TestLimitReadCloser(t *testing.T) {
|
||||||
@@ -78,11 +79,11 @@ func TestLimitedReadCloser_Read(t *testing.T) {
|
|||||||
buf := make([]byte, tc.limit+1)
|
buf := make([]byte, tc.limit+1)
|
||||||
|
|
||||||
lreader, err := LimitReadCloser(readCloser, tc.limit)
|
lreader, err := LimitReadCloser(readCloser, tc.limit)
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
n, err := lreader.Read(buf)
|
n, err := lreader.Read(buf)
|
||||||
assert.Equal(t, n, tc.want)
|
require.Equal(t, tc.err, err)
|
||||||
assert.Equal(t, tc.err, err)
|
assert.Equal(t, tc.want, n)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
internal/aghtest/os.go
Normal file
45
internal/aghtest/os.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package aghtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrepareTestDir returns the full path to temporary created directory and
|
||||||
|
// registers the appropriate cleanup for *t.
|
||||||
|
func PrepareTestDir(t *testing.T) (dir string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
dir, err = ioutil.TempDir(wd, "agh-test")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, dir)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
// TODO(e.burkov): Replace with t.TempDir methods after updating
|
||||||
|
// go version to 1.15.
|
||||||
|
start := time.Now()
|
||||||
|
for {
|
||||||
|
err := os.RemoveAll(dir)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" || time.Since(start) >= 500*time.Millisecond {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return dir
|
||||||
|
}
|
||||||
@@ -19,7 +19,12 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDiscoverTime = time.Second * 3
|
defaultDiscoverTime = time.Second * 3
|
||||||
leaseExpireStatic = 1
|
// leaseExpireStatic is used to define the Expiry field for static
|
||||||
|
// leases.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Remove it when static leases determining mechanism
|
||||||
|
// will be improved.
|
||||||
|
leaseExpireStatic = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
var webHandlersRegistered = false
|
var webHandlersRegistered = false
|
||||||
@@ -37,12 +42,24 @@ type Lease struct {
|
|||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface for *Lease.
|
// MarshalJSON implements the json.Marshaler interface for *Lease.
|
||||||
func (l *Lease) MarshalJSON() ([]byte, error) {
|
func (l *Lease) MarshalJSON() ([]byte, error) {
|
||||||
|
var expiryStr string
|
||||||
|
if expiry := l.Expiry; expiry.Unix() != leaseExpireStatic {
|
||||||
|
// The front-end is waiting for RFC 3999 format of the time
|
||||||
|
// value. It also shouldn't got an Expiry field for static
|
||||||
|
// leases.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2692.
|
||||||
|
expiryStr = expiry.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
type lease Lease
|
type lease Lease
|
||||||
return json.Marshal(&struct {
|
return json.Marshal(&struct {
|
||||||
HWAddr string `json:"mac"`
|
HWAddr string `json:"mac"`
|
||||||
|
Expiry string `json:"expires,omitempty"`
|
||||||
*lease
|
*lease
|
||||||
}{
|
}{
|
||||||
HWAddr: l.HWAddr.String(),
|
HWAddr: l.HWAddr.String(),
|
||||||
|
Expiry: expiryStr,
|
||||||
lease: (*lease)(l),
|
lease: (*lease)(l),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -117,14 +134,14 @@ type ServerInterface interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create - create object
|
// Create - create object
|
||||||
func Create(config ServerConfig) *Server {
|
func Create(conf ServerConfig) *Server {
|
||||||
s := &Server{}
|
s := &Server{}
|
||||||
|
|
||||||
s.conf.Enabled = config.Enabled
|
s.conf.Enabled = conf.Enabled
|
||||||
s.conf.InterfaceName = config.InterfaceName
|
s.conf.InterfaceName = conf.InterfaceName
|
||||||
s.conf.HTTPRegister = config.HTTPRegister
|
s.conf.HTTPRegister = conf.HTTPRegister
|
||||||
s.conf.ConfigModified = config.ConfigModified
|
s.conf.ConfigModified = conf.ConfigModified
|
||||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
s.conf.DBFilePath = filepath.Join(conf.WorkDir, dbFilename)
|
||||||
|
|
||||||
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@@ -145,7 +162,7 @@ func Create(config ServerConfig) *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err4, err6 error
|
var err4, err6 error
|
||||||
v4conf := config.Conf4
|
v4conf := conf.Conf4
|
||||||
v4conf.Enabled = s.conf.Enabled
|
v4conf.Enabled = s.conf.Enabled
|
||||||
if len(v4conf.RangeStart) == 0 {
|
if len(v4conf.RangeStart) == 0 {
|
||||||
v4conf.Enabled = false
|
v4conf.Enabled = false
|
||||||
@@ -154,7 +171,7 @@ func Create(config ServerConfig) *Server {
|
|||||||
v4conf.notify = s.onNotify
|
v4conf.notify = s.onNotify
|
||||||
s.srv4, err4 = v4Create(v4conf)
|
s.srv4, err4 = v4Create(v4conf)
|
||||||
|
|
||||||
v6conf := config.Conf6
|
v6conf := conf.Conf6
|
||||||
v6conf.Enabled = s.conf.Enabled
|
v6conf.Enabled = s.conf.Enabled
|
||||||
if len(v6conf.RangeStart) == 0 {
|
if len(v6conf.RangeStart) == 0 {
|
||||||
v6conf.Enabled = false
|
v6conf.Enabled = false
|
||||||
@@ -172,6 +189,9 @@ func Create(config ServerConfig) *Server {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.conf.Conf4 = conf.Conf4
|
||||||
|
s.conf.Conf6 = conf.Conf6
|
||||||
|
|
||||||
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
|
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
|
||||||
log.Error("Can't enable DHCP server because neither DHCPv4 nor DHCPv6 servers are configured")
|
log.Error("Can't enable DHCP server because neither DHCPv4 nor DHCPv6 servers are configured")
|
||||||
return nil
|
return nil
|
||||||
@@ -245,14 +265,10 @@ const (
|
|||||||
LeasesAll = LeasesDynamic | LeasesStatic
|
LeasesAll = LeasesDynamic | LeasesStatic
|
||||||
)
|
)
|
||||||
|
|
||||||
// Leases returns the list of current DHCP leases (thread-safe)
|
// Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for
|
||||||
func (s *Server) Leases(flags int) []Lease {
|
// concurrent use.
|
||||||
result := s.srv4.GetLeases(flags)
|
func (s *Server) Leases(flags int) (leases []Lease) {
|
||||||
|
return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
|
||||||
v6leases := s.srv6.GetLeases(flags)
|
|
||||||
result = append(result, v6leases...)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||||
@@ -290,14 +306,22 @@ func parseOptionString(s string) (uint8, []byte) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "ip":
|
case "ip":
|
||||||
ip := net.ParseIP(sval)
|
ip := net.ParseIP(sval)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
val = ip
|
|
||||||
|
|
||||||
|
// Most DHCP options require IPv4, so do not put the 16-byte
|
||||||
|
// version if we can. Otherwise, the clients will receive weird
|
||||||
|
// data that looks like four IPv4 addresses.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2688.
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
val = ip4
|
||||||
|
} else {
|
||||||
|
val = ip
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@@ -20,116 +20,171 @@ func TestMain(m *testing.M) {
|
|||||||
func testNotify(flags uint32) {
|
func testNotify(flags uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leases database store/load
|
// Leases database store/load.
|
||||||
func TestDB(t *testing.T) {
|
func TestDB(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
s := Server{}
|
s := Server{
|
||||||
s.conf.DBFilePath = dbFilename
|
conf: ServerConfig{
|
||||||
|
DBFilePath: dbFilename,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
conf := V4ServerConf{
|
s.srv4, err = v4Create(V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
GatewayIP: net.IP{192, 168, 10, 1},
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
notify: testNotify,
|
notify: testNotify,
|
||||||
}
|
})
|
||||||
s.srv4, err = v4Create(conf)
|
require.Nil(t, err)
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
s.srv6, err = v6Create(V6ServerConf{})
|
s.srv6, err = v6Create(V6ServerConf{})
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
l := Lease{}
|
leases := []Lease{{
|
||||||
l.IP = net.IP{192, 168, 10, 100}
|
IP: net.IP{192, 168, 10, 100},
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
exp1 := time.Now().Add(time.Hour)
|
Expiry: time.Now().Add(time.Hour),
|
||||||
l.Expiry = exp1
|
}, {
|
||||||
|
IP: net.IP{192, 168, 10, 101},
|
||||||
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB},
|
||||||
|
}}
|
||||||
|
|
||||||
srv4, ok := s.srv4.(*v4Server)
|
srv4, ok := s.srv4.(*v4Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
srv4.addLease(&l)
|
srv4.addLease(&leases[0])
|
||||||
|
require.Nil(t, s.srv4.AddStaticLease(leases[1]))
|
||||||
|
|
||||||
l2 := Lease{}
|
|
||||||
l2.IP = net.IP{192, 168, 10, 101}
|
|
||||||
l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb")
|
|
||||||
err = s.srv4.AddStaticLease(l2)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
_ = os.Remove("leases.db")
|
|
||||||
s.dbStore()
|
s.dbStore()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.Nil(t, os.Remove(dbFilename))
|
||||||
|
})
|
||||||
s.srv4.ResetLeases(nil)
|
s.srv4.ResetLeases(nil)
|
||||||
|
|
||||||
s.dbLoad()
|
s.dbLoad()
|
||||||
|
|
||||||
ll := s.srv4.GetLeases(LeasesAll)
|
ll := s.srv4.GetLeases(LeasesAll)
|
||||||
|
require.Len(t, ll, len(leases))
|
||||||
|
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String())
|
assert.Equal(t, leases[1].HWAddr, ll[0].HWAddr)
|
||||||
assert.True(t, net.IP{192, 168, 10, 101}.Equal(ll[0].IP))
|
assert.Equal(t, leases[1].IP, ll[0].IP)
|
||||||
assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix())
|
assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix())
|
||||||
|
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String())
|
assert.Equal(t, leases[0].HWAddr, ll[1].HWAddr)
|
||||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ll[1].IP))
|
assert.Equal(t, leases[0].IP, ll[1].IP)
|
||||||
assert.Equal(t, exp1.Unix(), ll[1].Expiry.Unix())
|
assert.Equal(t, leases[0].Expiry.Unix(), ll[1].Expiry.Unix())
|
||||||
|
|
||||||
_ = os.Remove("leases.db")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsValidSubnetMask(t *testing.T) {
|
func TestIsValidSubnetMask(t *testing.T) {
|
||||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 255, 0}))
|
testCases := []struct {
|
||||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 254, 0}))
|
mask net.IP
|
||||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 252, 0}))
|
want bool
|
||||||
assert.False(t, isValidSubnetMask([]byte{255, 255, 253, 0}))
|
}{{
|
||||||
assert.False(t, isValidSubnetMask([]byte{255, 255, 255, 1}))
|
mask: net.IP{255, 255, 255, 0},
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
mask: net.IP{255, 255, 254, 0},
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
mask: net.IP{255, 255, 252, 0},
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
mask: net.IP{255, 255, 253, 0},
|
||||||
|
}, {
|
||||||
|
mask: net.IP{255, 255, 255, 1},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.mask.String(), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, isValidSubnetMask(tc.mask))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeLeases(t *testing.T) {
|
func TestNormalizeLeases(t *testing.T) {
|
||||||
dynLeases := []*Lease{}
|
dynLeases := []*Lease{{
|
||||||
staticLeases := []*Lease{}
|
HWAddr: net.HardwareAddr{1, 2, 3, 4},
|
||||||
|
}, {
|
||||||
|
HWAddr: net.HardwareAddr{1, 2, 3, 5},
|
||||||
|
}}
|
||||||
|
|
||||||
lease := &Lease{}
|
staticLeases := []*Lease{{
|
||||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
HWAddr: net.HardwareAddr{1, 2, 3, 4},
|
||||||
dynLeases = append(dynLeases, lease)
|
IP: net.IP{0, 2, 3, 4},
|
||||||
lease = new(Lease)
|
}, {
|
||||||
lease.HWAddr = []byte{1, 2, 3, 5}
|
HWAddr: net.HardwareAddr{2, 2, 3, 4},
|
||||||
dynLeases = append(dynLeases, lease)
|
}}
|
||||||
|
|
||||||
lease = new(Lease)
|
|
||||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
|
||||||
lease.IP = []byte{0, 2, 3, 4}
|
|
||||||
staticLeases = append(staticLeases, lease)
|
|
||||||
lease = new(Lease)
|
|
||||||
lease.HWAddr = []byte{2, 2, 3, 4}
|
|
||||||
staticLeases = append(staticLeases, lease)
|
|
||||||
|
|
||||||
leases := normalizeLeases(staticLeases, dynLeases)
|
leases := normalizeLeases(staticLeases, dynLeases)
|
||||||
|
require.Len(t, leases, 3)
|
||||||
|
|
||||||
assert.Len(t, leases, 3)
|
assert.Equal(t, leases[0].HWAddr, dynLeases[0].HWAddr)
|
||||||
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
assert.Equal(t, leases[0].IP, staticLeases[0].IP)
|
||||||
assert.True(t, bytes.Equal(leases[0].IP, []byte{0, 2, 3, 4}))
|
assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr)
|
||||||
assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4}))
|
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
||||||
assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOptions(t *testing.T) {
|
func TestOptions(t *testing.T) {
|
||||||
code, val := parseOptionString(" 12 hex abcdef ")
|
testCases := []struct {
|
||||||
assert.EqualValues(t, 12, code)
|
name string
|
||||||
assert.True(t, bytes.Equal([]byte{0xab, 0xcd, 0xef}, val))
|
optStr string
|
||||||
|
wantVal []byte
|
||||||
|
wantCode uint8
|
||||||
|
}{{
|
||||||
|
name: "success_hex",
|
||||||
|
optStr: "12 hex abcdef",
|
||||||
|
wantVal: []byte{0xab, 0xcd, 0xef},
|
||||||
|
wantCode: 12,
|
||||||
|
}, {
|
||||||
|
name: "bad_hex",
|
||||||
|
optStr: "12 hex abcdefx",
|
||||||
|
wantVal: nil,
|
||||||
|
wantCode: 0,
|
||||||
|
}, {
|
||||||
|
name: "success_ip",
|
||||||
|
optStr: "123 ip 1.2.3.4",
|
||||||
|
wantVal: net.IP{1, 2, 3, 4},
|
||||||
|
wantCode: 123,
|
||||||
|
}, {
|
||||||
|
name: "success_ipv6",
|
||||||
|
optStr: "123 ip ::1234",
|
||||||
|
wantVal: net.IP{
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0x12, 0x34,
|
||||||
|
},
|
||||||
|
wantCode: 123,
|
||||||
|
}, {
|
||||||
|
name: "bad_code",
|
||||||
|
optStr: "256 ip 1.1.1.1",
|
||||||
|
wantVal: nil,
|
||||||
|
wantCode: 0,
|
||||||
|
}, {
|
||||||
|
name: "negative_code",
|
||||||
|
optStr: "-1 ip 1.1.1.1",
|
||||||
|
wantVal: nil,
|
||||||
|
wantCode: 0,
|
||||||
|
}, {
|
||||||
|
name: "bad_ip",
|
||||||
|
optStr: "12 ip 1.1.1.1x",
|
||||||
|
wantVal: nil,
|
||||||
|
wantCode: 0,
|
||||||
|
}, {
|
||||||
|
name: "bad_mode",
|
||||||
|
wantVal: nil,
|
||||||
|
optStr: "12 x 1.1.1.1",
|
||||||
|
wantCode: 0,
|
||||||
|
}}
|
||||||
|
|
||||||
code, _ = parseOptionString(" 12 hex abcdef1 ")
|
for _, tc := range testCases {
|
||||||
assert.EqualValues(t, 0, code)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
code, val := parseOptionString(tc.optStr)
|
||||||
code, val = parseOptionString("123 ip 1.2.3.4")
|
require.Equal(t, tc.wantCode, code)
|
||||||
assert.EqualValues(t, 123, code)
|
if tc.wantVal != nil {
|
||||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(val)))
|
assert.Equal(t, tc.wantVal, val)
|
||||||
|
}
|
||||||
code, _ = parseOptionString("256 ip 1.1.1.1")
|
})
|
||||||
assert.EqualValues(t, 0, code)
|
}
|
||||||
code, _ = parseOptionString("-1 ip 1.1.1.1")
|
|
||||||
assert.EqualValues(t, 0, code)
|
|
||||||
code, _ = parseOptionString("12 ip 1.1.1.1x")
|
|
||||||
assert.EqualValues(t, 0, code)
|
|
||||||
code, _ = parseOptionString("12 x 1.1.1.1")
|
|
||||||
assert.EqualValues(t, 0, code)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dhcpd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@@ -11,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/jsonutil"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +29,11 @@ type v4ServerConfJSON struct {
|
|||||||
LeaseDuration uint32 `json:"lease_duration"`
|
LeaseDuration uint32 `json:"lease_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf {
|
func v4JSONToServerConf(j *v4ServerConfJSON) V4ServerConf {
|
||||||
|
if j == nil {
|
||||||
|
return V4ServerConf{}
|
||||||
|
}
|
||||||
|
|
||||||
return V4ServerConf{
|
return V4ServerConf{
|
||||||
GatewayIP: j.GatewayIP,
|
GatewayIP: j.GatewayIP,
|
||||||
SubnetMask: j.SubnetMask,
|
SubnetMask: j.SubnetMask,
|
||||||
@@ -44,7 +48,11 @@ type v6ServerConfJSON struct {
|
|||||||
LeaseDuration uint32 `json:"lease_duration"`
|
LeaseDuration uint32 `json:"lease_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func v6JSONToServerConf(j v6ServerConfJSON) V6ServerConf {
|
func v6JSONToServerConf(j *v6ServerConfJSON) V6ServerConf {
|
||||||
|
if j == nil {
|
||||||
|
return V6ServerConf{}
|
||||||
|
}
|
||||||
|
|
||||||
return V6ServerConf{
|
return V6ServerConf{
|
||||||
RangeStart: j.RangeStart,
|
RangeStart: j.RangeStart,
|
||||||
LeaseDuration: j.LeaseDuration,
|
LeaseDuration: j.LeaseDuration,
|
||||||
@@ -83,24 +91,44 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dhcpServerConfigJSON struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
InterfaceName string `json:"interface_name"`
|
|
||||||
V4 v4ServerConfJSON `json:"v4"`
|
|
||||||
V6 v6ServerConfJSON `json:"v6"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) enableDHCP(ifaceName string) (code int, err error) {
|
func (s *Server) enableDHCP(ifaceName string) (code int, err error) {
|
||||||
var hasStaticIP bool
|
var hasStaticIP bool
|
||||||
hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName)
|
hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err)
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
// ErrPermission may happen here on Linux systems where
|
||||||
|
// AdGuard Home is installed using Snap. That doesn't
|
||||||
|
// necessarily mean that the machine doesn't have
|
||||||
|
// a static IP, so we can assume that it has and go on.
|
||||||
|
// If the machine doesn't, we'll get an error later.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2667.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): I was thinking about moving this
|
||||||
|
// into IfaceHasStaticIP, but then we wouldn't be able
|
||||||
|
// to log it. Think about it more.
|
||||||
|
log.Info("error while checking static ip: %s; "+
|
||||||
|
"assuming machine has static ip and going on", err)
|
||||||
|
hasStaticIP = true
|
||||||
|
} else if errors.Is(err, sysutil.ErrNoStaticIPInfo) {
|
||||||
|
// Couldn't obtain a definitive answer. Assume static
|
||||||
|
// IP an go on.
|
||||||
|
log.Info("can't check for static ip; " +
|
||||||
|
"assuming machine has static ip and going on")
|
||||||
|
hasStaticIP = true
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("checking static ip: %w", err)
|
||||||
|
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasStaticIP {
|
if !hasStaticIP {
|
||||||
err = sysutil.IfaceSetStaticIP(ifaceName)
|
err = sysutil.IfaceSetStaticIP(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, fmt.Errorf("setting static ip: %w", err)
|
err = fmt.Errorf("setting static ip: %w", err)
|
||||||
|
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,14 +140,22 @@ func (s *Server) enableDHCP(ifaceName string) (code int, err error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
type dhcpServerConfigJSON struct {
|
||||||
newconfig := dhcpServerConfigJSON{}
|
V4 *v4ServerConfJSON `json:"v4"`
|
||||||
newconfig.Enabled = s.conf.Enabled
|
V6 *v6ServerConfJSON `json:"v6"`
|
||||||
newconfig.InterfaceName = s.conf.InterfaceName
|
InterfaceName string `json:"interface_name"`
|
||||||
|
Enabled nullBool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
js, err := jsonutil.DecodeObject(&newconfig, r.Body)
|
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conf := dhcpServerConfigJSON{}
|
||||||
|
conf.Enabled = boolToNullBool(s.conf.Enabled)
|
||||||
|
conf.InterfaceName = s.conf.InterfaceName
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
|
httpError(r, w, http.StatusBadRequest,
|
||||||
|
"failed to parse new dhcp config json: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -129,62 +165,72 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
v4Enabled := false
|
v4Enabled := false
|
||||||
v6Enabled := false
|
v6Enabled := false
|
||||||
|
|
||||||
if js.Exists("v4") {
|
if conf.V4 != nil {
|
||||||
v4conf := v4JSONToServerConf(newconfig.V4)
|
v4Conf := v4JSONToServerConf(conf.V4)
|
||||||
v4conf.Enabled = newconfig.Enabled
|
v4Conf.Enabled = conf.Enabled == nbTrue
|
||||||
if len(v4conf.RangeStart) == 0 {
|
if len(v4Conf.RangeStart) == 0 {
|
||||||
v4conf.Enabled = false
|
v4Conf.Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
v4Enabled = v4conf.Enabled
|
v4Enabled = v4Conf.Enabled
|
||||||
v4conf.InterfaceName = newconfig.InterfaceName
|
v4Conf.InterfaceName = conf.InterfaceName
|
||||||
|
|
||||||
c4 := V4ServerConf{}
|
c4 := V4ServerConf{}
|
||||||
s.srv4.WriteDiskConfig4(&c4)
|
s.srv4.WriteDiskConfig4(&c4)
|
||||||
v4conf.notify = c4.notify
|
v4Conf.notify = c4.notify
|
||||||
v4conf.ICMPTimeout = c4.ICMPTimeout
|
v4Conf.ICMPTimeout = c4.ICMPTimeout
|
||||||
|
|
||||||
s4, err = v4Create(v4conf)
|
s4, err = v4Create(v4Conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(r, w, http.StatusBadRequest, "invalid dhcpv4 configuration: %s", err)
|
httpError(r, w, http.StatusBadRequest,
|
||||||
|
"invalid dhcpv4 configuration: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("v6") {
|
if conf.V6 != nil {
|
||||||
v6conf := v6JSONToServerConf(newconfig.V6)
|
v6Conf := v6JSONToServerConf(conf.V6)
|
||||||
v6conf.Enabled = newconfig.Enabled
|
v6Conf.Enabled = conf.Enabled == nbTrue
|
||||||
if len(v6conf.RangeStart) == 0 {
|
if len(v6Conf.RangeStart) == 0 {
|
||||||
v6conf.Enabled = false
|
v6Conf.Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
v6Enabled = v6conf.Enabled
|
// Don't overwrite the RA/SLAAC settings from the config file.
|
||||||
v6conf.InterfaceName = newconfig.InterfaceName
|
//
|
||||||
v6conf.notify = s.onNotify
|
// TODO(a.garipov): Perhaps include them into the request to
|
||||||
|
// allow changing them from the HTTP API?
|
||||||
|
v6Conf.RASLAACOnly = s.conf.Conf6.RASLAACOnly
|
||||||
|
v6Conf.RAAllowSLAAC = s.conf.Conf6.RAAllowSLAAC
|
||||||
|
|
||||||
s6, err = v6Create(v6conf)
|
v6Enabled = v6Conf.Enabled
|
||||||
|
v6Conf.InterfaceName = conf.InterfaceName
|
||||||
|
v6Conf.notify = s.onNotify
|
||||||
|
|
||||||
|
s6, err = v6Create(v6Conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(r, w, http.StatusBadRequest, "invalid dhcpv6 configuration: %s", err)
|
httpError(r, w, http.StatusBadRequest,
|
||||||
|
"invalid dhcpv6 configuration: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newconfig.Enabled && !v4Enabled && !v6Enabled {
|
if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled {
|
||||||
httpError(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
|
httpError(r, w, http.StatusBadRequest,
|
||||||
|
"dhcpv4 or dhcpv6 configuration must be complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Stop()
|
s.Stop()
|
||||||
|
|
||||||
if js.Exists("enabled") {
|
if conf.Enabled != nbNull {
|
||||||
s.conf.Enabled = newconfig.Enabled
|
s.conf.Enabled = conf.Enabled == nbTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("interface_name") {
|
if conf.InterfaceName != "" {
|
||||||
s.conf.InterfaceName = newconfig.InterfaceName
|
s.conf.InterfaceName = conf.InterfaceName
|
||||||
}
|
}
|
||||||
|
|
||||||
if s4 != nil {
|
if s4 != nil {
|
||||||
@@ -200,7 +246,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if s.conf.Enabled {
|
if s.conf.Enabled {
|
||||||
var code int
|
var code int
|
||||||
code, err = s.enableDHCP(newconfig.InterfaceName)
|
code, err = s.enableDHCP(conf.InterfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(r, w, code, "enabling dhcp: %s", err)
|
httpError(r, w, code, "enabling dhcp: %s", err)
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_notImplemented(t *testing.T) {
|
func TestServer_notImplemented(t *testing.T) {
|
||||||
@@ -14,7 +15,7 @@ func TestServer_notImplemented(t *testing.T) {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r, err := http.NewRequest(http.MethodGet, "/unsupported", nil)
|
r, err := http.NewRequest(http.MethodGet, "/unsupported", nil)
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
h(w, r)
|
h(w, r)
|
||||||
assert.Equal(t, http.StatusNotImplemented, w.Code)
|
assert.Equal(t, http.StatusNotImplemented, w.Code)
|
||||||
58
internal/dhcpd/nullbool.go
Normal file
58
internal/dhcpd/nullbool.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
69
internal/dhcpd/nullbool_test.go
Normal file
69
internal/dhcpd/nullbool_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNullBool_UnmarshalText(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
wantErrMsg string
|
||||||
|
want nullBool
|
||||||
|
}{{
|
||||||
|
name: "empty",
|
||||||
|
data: []byte{},
|
||||||
|
wantErrMsg: "",
|
||||||
|
want: nbNull,
|
||||||
|
}, {
|
||||||
|
name: "null",
|
||||||
|
data: []byte("null"),
|
||||||
|
wantErrMsg: "",
|
||||||
|
want: nbNull,
|
||||||
|
}, {
|
||||||
|
name: "true",
|
||||||
|
data: []byte("true"),
|
||||||
|
wantErrMsg: "",
|
||||||
|
want: nbTrue,
|
||||||
|
}, {
|
||||||
|
name: "false",
|
||||||
|
data: []byte("false"),
|
||||||
|
wantErrMsg: "",
|
||||||
|
want: nbFalse,
|
||||||
|
}, {
|
||||||
|
name: "invalid",
|
||||||
|
data: []byte("flase"),
|
||||||
|
wantErrMsg: `invalid nullBool value "flase"`,
|
||||||
|
want: nbNull,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var got nullBool
|
||||||
|
err := got.UnmarshalJSON(tc.data)
|
||||||
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Nil(t, err)
|
||||||
|
assert.Equal(t, want, got.A)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type raCtx struct {
|
type raCtx struct {
|
||||||
raAllowSlaac bool // send RA packets without MO flags
|
raAllowSLAAC bool // send RA packets without MO flags
|
||||||
raSlaacOnly bool // send RA packets with MO flags
|
raSLAACOnly bool // send RA packets with MO flags
|
||||||
ipAddr net.IP // source IP address (link-local-unicast)
|
ipAddr net.IP // source IP address (link-local-unicast)
|
||||||
dnsIPAddr net.IP // IP address for DNS Server option
|
dnsIPAddr net.IP // IP address for DNS Server option
|
||||||
prefixIPAddr net.IP // IP address for Prefix option
|
prefixIPAddr net.IP // IP address for Prefix option
|
||||||
@@ -159,7 +159,7 @@ func createICMPv6RAPacket(params icmpv6RA) []byte {
|
|||||||
func (ra *raCtx) Init() error {
|
func (ra *raCtx) Init() error {
|
||||||
ra.stop.Store(0)
|
ra.stop.Store(0)
|
||||||
ra.conn = nil
|
ra.conn = nil
|
||||||
if !(ra.raAllowSlaac || ra.raSlaacOnly) {
|
if !(ra.raAllowSLAAC || ra.raSLAACOnly) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +167,8 @@ func (ra *raCtx) Init() error {
|
|||||||
ra.ipAddr, ra.dnsIPAddr)
|
ra.ipAddr, ra.dnsIPAddr)
|
||||||
|
|
||||||
params := icmpv6RA{
|
params := icmpv6RA{
|
||||||
managedAddressConfiguration: !ra.raSlaacOnly,
|
managedAddressConfiguration: !ra.raSLAACOnly,
|
||||||
otherConfiguration: !ra.raSlaacOnly,
|
otherConfiguration: !ra.raSLAACOnly,
|
||||||
mtu: uint32(ra.iface.MTU),
|
mtu: uint32(ra.iface.MTU),
|
||||||
prefixLen: 64,
|
prefixLen: 64,
|
||||||
recursiveDNSServer: ra.dnsIPAddr,
|
recursiveDNSServer: ra.dnsIPAddr,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRA(t *testing.T) {
|
func TestRA(t *testing.T) {
|
||||||
ra := icmpv6RA{
|
data := createICMPv6RAPacket(icmpv6RA{
|
||||||
managedAddressConfiguration: false,
|
managedAddressConfiguration: false,
|
||||||
otherConfiguration: true,
|
otherConfiguration: true,
|
||||||
mtu: 1500,
|
mtu: 1500,
|
||||||
@@ -17,8 +16,7 @@ func TestRA(t *testing.T) {
|
|||||||
prefixLen: 64,
|
prefixLen: 64,
|
||||||
recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"),
|
recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"),
|
||||||
sourceLinkLayerAddress: []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00},
|
sourceLinkLayerAddress: []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00},
|
||||||
}
|
})
|
||||||
data := createICMPv6RAPacket(ra)
|
|
||||||
dataCorrect := []byte{
|
dataCorrect := []byte{
|
||||||
0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00,
|
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||||
@@ -27,5 +25,5 @@ func TestRA(t *testing.T) {
|
|||||||
0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x08, 0x00, 0x27, 0xff, 0xfe, 0x00, 0x00, 0x00,
|
0x08, 0x00, 0x27, 0xff, 0xfe, 0x00, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
assert.True(t, bytes.Equal(data, dataCorrect))
|
assert.Equal(t, dataCorrect, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,12 +79,12 @@ type V6ServerConf struct {
|
|||||||
|
|
||||||
// The first IP address for dynamic leases
|
// The first IP address for dynamic leases
|
||||||
// The last allowed IP address ends with 0xff byte
|
// The last allowed IP address ends with 0xff byte
|
||||||
RangeStart net.IP `yaml:"range_start"`
|
RangeStart net.IP `yaml:"range_start" json:"range_start"`
|
||||||
|
|
||||||
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
||||||
|
|
||||||
RaSlaacOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags
|
RASLAACOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags
|
||||||
RaAllowSlaac bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags
|
RAAllowSLAAC bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags
|
||||||
|
|
||||||
ipStart net.IP // starting IP address for dynamic leases
|
ipStart net.IP // starting IP address for dynamic leases
|
||||||
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ type v4Server struct {
|
|||||||
srv *server4.Server
|
srv *server4.Server
|
||||||
leasesLock sync.Mutex
|
leasesLock sync.Mutex
|
||||||
leases []*Lease
|
leases []*Lease
|
||||||
ipAddrs [256]byte
|
// TODO(e.burkov): This field type should be a normal bitmap.
|
||||||
|
ipAddrs [256]byte
|
||||||
|
|
||||||
conf V4ServerConf
|
conf V4ServerConf
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeIface struct {
|
type fakeIface struct {
|
||||||
@@ -79,8 +80,8 @@ func TestIfaceIPAddrs(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv)
|
got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv)
|
||||||
|
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,12 +141,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
|||||||
want: nil,
|
want: nil,
|
||||||
wantErr: errTest,
|
wantErr: errTest,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv4_wait",
|
name: "ipv4_wait",
|
||||||
iface: &waitingFakeIface{
|
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
|
||||||
addrs: []net.Addr{addr4},
|
|
||||||
err: nil,
|
|
||||||
n: 1,
|
|
||||||
},
|
|
||||||
ipv: ipVersion4,
|
ipv: ipVersion4,
|
||||||
want: []net.IP{ip4, ip4},
|
want: []net.IP{ip4, ip4},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
@@ -168,12 +165,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
|||||||
want: nil,
|
want: nil,
|
||||||
wantErr: errTest,
|
wantErr: errTest,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv6_wait",
|
name: "ipv6_wait",
|
||||||
iface: &waitingFakeIface{
|
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
|
||||||
addrs: []net.Addr{addr6},
|
|
||||||
err: nil,
|
|
||||||
n: 1,
|
|
||||||
},
|
|
||||||
ipv: ipVersion6,
|
ipv: ipVersion6,
|
||||||
want: []net.IP{ip6, ip6},
|
want: []net.IP{ip6, ip6},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
@@ -182,8 +175,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
||||||
|
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,172 +8,182 @@ import (
|
|||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notify4(flags uint32) {
|
func notify4(flags uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4StaticLeaseAddRemove(t *testing.T) {
|
func TestV4_AddRemove_static(t *testing.T) {
|
||||||
conf := V4ServerConf{
|
s, err := v4Create(V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
GatewayIP: net.IP{192, 168, 10, 1},
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
notify: notify4,
|
notify: notify4,
|
||||||
}
|
})
|
||||||
s, err := v4Create(conf)
|
require.Nil(t, err)
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Empty(t, ls)
|
assert.Empty(t, ls)
|
||||||
|
|
||||||
// add static lease
|
// Add static lease.
|
||||||
l := Lease{}
|
l := Lease{
|
||||||
l.IP = net.IP{192, 168, 10, 150}
|
IP: net.IP{192, 168, 10, 150},
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
}
|
||||||
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
// try to add the same static lease - fail
|
|
||||||
assert.NotNil(t, s.AddStaticLease(l))
|
assert.NotNil(t, s.AddStaticLease(l))
|
||||||
|
|
||||||
// check
|
|
||||||
ls = s.GetLeases(LeasesStatic)
|
ls = s.GetLeases(LeasesStatic)
|
||||||
assert.Len(t, ls, 1)
|
require.Len(t, ls, 1)
|
||||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
assert.True(t, l.IP.Equal(ls[0].IP))
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||||
|
|
||||||
// try to remove static lease - fail
|
// Try to remove static lease.
|
||||||
l.IP = net.IP{192, 168, 10, 110}
|
assert.NotNil(t, s.RemoveStaticLease(Lease{
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
IP: net.IP{192, 168, 10, 110},
|
||||||
assert.NotNil(t, s.RemoveStaticLease(l))
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}))
|
||||||
|
|
||||||
// remove static lease
|
// Remove static lease.
|
||||||
l.IP = net.IP{192, 168, 10, 150}
|
require.Nil(t, s.RemoveStaticLease(l))
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
assert.Nil(t, s.RemoveStaticLease(l))
|
|
||||||
|
|
||||||
// check
|
|
||||||
ls = s.GetLeases(LeasesStatic)
|
ls = s.GetLeases(LeasesStatic)
|
||||||
assert.Empty(t, ls)
|
assert.Empty(t, ls)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) {
|
func TestV4_AddReplace(t *testing.T) {
|
||||||
conf := V4ServerConf{
|
sIface, err := v4Create(V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
GatewayIP: net.IP{192, 168, 10, 1},
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
notify: notify4,
|
notify: notify4,
|
||||||
}
|
})
|
||||||
sIface, err := v4Create(conf)
|
require.Nil(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// add dynamic lease
|
dynLeases := []Lease{{
|
||||||
ld := Lease{}
|
IP: net.IP{192, 168, 10, 150},
|
||||||
ld.IP = net.IP{192, 168, 10, 150}
|
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa")
|
}, {
|
||||||
s.addLease(&ld)
|
IP: net.IP{192, 168, 10, 151},
|
||||||
|
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}}
|
||||||
|
|
||||||
// add dynamic lease
|
for i := range dynLeases {
|
||||||
{
|
s.addLease(&dynLeases[i])
|
||||||
ld := Lease{}
|
|
||||||
ld.IP = net.IP{192, 168, 10, 151}
|
|
||||||
ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
|
||||||
s.addLease(&ld)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add static lease with the same IP
|
stLeases := []Lease{{
|
||||||
l := Lease{}
|
IP: net.IP{192, 168, 10, 150},
|
||||||
l.IP = net.IP{192, 168, 10, 150}
|
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa")
|
}, {
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
IP: net.IP{192, 168, 10, 152},
|
||||||
|
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}}
|
||||||
|
|
||||||
// add static lease with the same MAC
|
for _, l := range stLeases {
|
||||||
l = Lease{}
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
l.IP = net.IP{192, 168, 10, 152}
|
}
|
||||||
l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
|
||||||
|
|
||||||
// check
|
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Len(t, ls, 2)
|
require.Len(t, ls, 2)
|
||||||
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
for i, l := range ls {
|
||||||
assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.True(t, stLeases[i].IP.Equal(l.IP))
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
||||||
|
assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
|
||||||
assert.True(t, net.IP{192, 168, 10, 152}.Equal(ls[1].IP))
|
}
|
||||||
assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String())
|
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4StaticLeaseGet(t *testing.T) {
|
func TestV4StaticLease_Get(t *testing.T) {
|
||||||
conf := V4ServerConf{
|
var err error
|
||||||
|
sIface, err := v4Create(V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
GatewayIP: net.IP{192, 168, 10, 1},
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
SubnetMask: net.IP{255, 255, 255, 0},
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
notify: notify4,
|
notify: notify4,
|
||||||
}
|
})
|
||||||
sIface, err := v4Create(conf)
|
require.Nil(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||||
|
|
||||||
l := Lease{}
|
l := Lease{
|
||||||
l.IP = net.IP{192, 168, 10, 150}
|
IP: net.IP{192, 168, 10, 150},
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
}
|
||||||
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
|
|
||||||
// "Discover"
|
var req, resp *dhcpv4.DHCPv4
|
||||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
req, _ := dhcpv4.NewDiscovery(mac)
|
|
||||||
resp, _ := dhcpv4.NewReplyFromRequest(req)
|
|
||||||
assert.Equal(t, 1, s.process(req, resp))
|
|
||||||
|
|
||||||
// check "Offer"
|
t.Run("discover", func(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
var err error
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr))
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
|
||||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
|
||||||
|
|
||||||
// "Request"
|
req, err = dhcpv4.NewDiscovery(mac)
|
||||||
req, _ = dhcpv4.NewRequestFromOffer(resp)
|
require.Nil(t, err)
|
||||||
resp, _ = dhcpv4.NewReplyFromRequest(req)
|
|
||||||
assert.Equal(t, 1, s.process(req, resp))
|
|
||||||
|
|
||||||
// check "Ack"
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
require.Nil(t, err)
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
assert.Equal(t, 1, s.process(req, resp))
|
||||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr))
|
})
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
require.Nil(t, err)
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
|
||||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
t.Run("offer", func(t *testing.T) {
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||||
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
|
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||||
|
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||||
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("request", func(t *testing.T) {
|
||||||
|
req, err = dhcpv4.NewRequestFromOffer(resp)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, s.process(req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("ack", func(t *testing.T) {
|
||||||
|
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||||
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
|
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||||
|
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||||
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
|
})
|
||||||
|
|
||||||
dnsAddrs := resp.DNS()
|
dnsAddrs := resp.DNS()
|
||||||
assert.Len(t, dnsAddrs, 1)
|
require.Len(t, dnsAddrs, 1)
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0]))
|
assert.True(t, s.conf.GatewayIP.Equal(dnsAddrs[0]))
|
||||||
|
|
||||||
// check lease
|
t.Run("check_lease", func(t *testing.T) {
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Len(t, ls, 1)
|
require.Len(t, ls, 1)
|
||||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
assert.True(t, l.IP.Equal(ls[0].IP))
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.Equal(t, mac, ls[0].HWAddr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4DynamicLeaseGet(t *testing.T) {
|
func TestV4DynamicLease_Get(t *testing.T) {
|
||||||
conf := V4ServerConf{
|
var err error
|
||||||
|
sIface, err := v4Create(V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.IP{192, 168, 10, 100},
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
@@ -184,58 +194,97 @@ func TestV4DynamicLeaseGet(t *testing.T) {
|
|||||||
"81 hex 303132",
|
"81 hex 303132",
|
||||||
"82 ip 1.2.3.4",
|
"82 ip 1.2.3.4",
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
sIface, err := v4Create(conf)
|
require.Nil(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||||
|
|
||||||
// "Discover"
|
var req, resp *dhcpv4.DHCPv4
|
||||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
req, _ := dhcpv4.NewDiscovery(mac)
|
|
||||||
resp, _ := dhcpv4.NewReplyFromRequest(req)
|
|
||||||
assert.Equal(t, 1, s.process(req, resp))
|
|
||||||
|
|
||||||
// check "Offer"
|
t.Run("discover", func(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
req, err = dhcpv4.NewDiscovery(mac)
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
require.Nil(t, err)
|
||||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr))
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
|
||||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
|
||||||
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
|
||||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)])))
|
|
||||||
|
|
||||||
// "Request"
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
req, _ = dhcpv4.NewRequestFromOffer(resp)
|
require.Nil(t, err)
|
||||||
resp, _ = dhcpv4.NewReplyFromRequest(req)
|
assert.Equal(t, 1, s.process(req, resp))
|
||||||
assert.Equal(t, 1, s.process(req, resp))
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
// check "Ack"
|
t.Run("offer", func(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr))
|
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
|
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
||||||
|
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)])))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("request", func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
req, err = dhcpv4.NewRequestFromOffer(resp)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, s.process(req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("ack", func(t *testing.T) {
|
||||||
|
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||||
|
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||||
|
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||||
|
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||||
|
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||||
|
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||||
|
})
|
||||||
|
|
||||||
dnsAddrs := resp.DNS()
|
dnsAddrs := resp.DNS()
|
||||||
assert.Len(t, dnsAddrs, 1)
|
require.Len(t, dnsAddrs, 1)
|
||||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0]))
|
assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0]))
|
||||||
|
|
||||||
// check lease
|
// check lease
|
||||||
ls := s.GetLeases(LeasesDynamic)
|
t.Run("check_lease", func(t *testing.T) {
|
||||||
assert.Len(t, ls, 1)
|
ls := s.GetLeases(LeasesDynamic)
|
||||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP))
|
assert.Len(t, ls, 1)
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP))
|
||||||
|
assert.Equal(t, mac, ls[0].HWAddr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIP4InRange(t *testing.T) {
|
||||||
start := net.IP{192, 168, 10, 100}
|
start := net.IP{192, 168, 10, 100}
|
||||||
stop := net.IP{192, 168, 10, 200}
|
stop := net.IP{192, 168, 10, 200}
|
||||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 10, 99}))
|
|
||||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 100}))
|
testCases := []struct {
|
||||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 201}))
|
ip net.IP
|
||||||
assert.True(t, ip4InRange(start, stop, net.IP{192, 168, 10, 100}))
|
want bool
|
||||||
|
}{{
|
||||||
|
ip: net.IP{192, 168, 10, 99},
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
ip: net.IP{192, 168, 11, 100},
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
ip: net.IP{192, 168, 11, 201},
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
ip: start,
|
||||||
|
want: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.ip.String(), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, ip4InRange(start, stop, tc.ip))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -552,8 +552,8 @@ func (s *v6Server) initRA(iface *net.Interface) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ra.raAllowSlaac = s.conf.RaAllowSlaac
|
s.ra.raAllowSLAAC = s.conf.RAAllowSLAAC
|
||||||
s.ra.raSlaacOnly = s.conf.RaSlaacOnly
|
s.ra.raSLAACOnly = s.conf.RASLAACOnly
|
||||||
s.ra.dnsIPAddr = s.ra.ipAddr
|
s.ra.dnsIPAddr = s.ra.ipAddr
|
||||||
s.ra.prefixIPAddr = s.conf.ipStart
|
s.ra.prefixIPAddr = s.conf.ipStart
|
||||||
s.ra.ifaceName = s.conf.InterfaceName
|
s.ra.ifaceName = s.conf.InterfaceName
|
||||||
@@ -594,7 +594,7 @@ func (s *v6Server) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
|
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
|
||||||
if s.conf.RaSlaacOnly {
|
if s.conf.RASLAACOnly {
|
||||||
log.Debug("DHCPv6: not starting DHCPv6 server due to ra_slaac_only=true")
|
log.Debug("DHCPv6: not starting DHCPv6 server due to ra_slaac_only=true")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,220 +9,283 @@ import (
|
|||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
"github.com/insomniacslk/dhcp/iana"
|
"github.com/insomniacslk/dhcp/iana"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notify6(flags uint32) {
|
func notify6(flags uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV6StaticLeaseAddRemove(t *testing.T) {
|
func TestV6_AddRemove_static(t *testing.T) {
|
||||||
conf := V6ServerConf{
|
s, err := v6Create(V6ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.ParseIP("2001::1"),
|
RangeStart: net.ParseIP("2001::1"),
|
||||||
notify: notify6,
|
notify: notify6,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
require.Empty(t, s.GetLeases(LeasesStatic))
|
||||||
|
|
||||||
|
// Add static lease.
|
||||||
|
l := Lease{
|
||||||
|
IP: net.ParseIP("2001::1"),
|
||||||
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
}
|
}
|
||||||
s, err := v6Create(conf)
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
assert.Nil(t, err)
|
|
||||||
|
// Try to add the same static lease.
|
||||||
|
require.NotNil(t, s.AddStaticLease(l))
|
||||||
|
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Empty(t, ls)
|
require.Len(t, ls, 1)
|
||||||
|
assert.Equal(t, l.IP, ls[0].IP)
|
||||||
// add static lease
|
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||||
l := Lease{}
|
|
||||||
l.IP = net.ParseIP("2001::1")
|
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
|
||||||
|
|
||||||
// try to add static lease - fail
|
|
||||||
assert.NotNil(t, s.AddStaticLease(l))
|
|
||||||
|
|
||||||
// check
|
|
||||||
ls = s.GetLeases(LeasesStatic)
|
|
||||||
assert.Len(t, ls, 1)
|
|
||||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||||
|
|
||||||
// try to remove static lease - fail
|
// Try to remove non-existent static lease.
|
||||||
l.IP = net.ParseIP("2001::2")
|
require.NotNil(t, s.RemoveStaticLease(Lease{
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
IP: net.ParseIP("2001::2"),
|
||||||
assert.NotNil(t, s.RemoveStaticLease(l))
|
HWAddr: l.HWAddr,
|
||||||
|
}))
|
||||||
|
|
||||||
// remove static lease
|
// Remove static lease.
|
||||||
l.IP = net.ParseIP("2001::1")
|
require.Nil(t, s.RemoveStaticLease(l))
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
assert.Nil(t, s.RemoveStaticLease(l))
|
|
||||||
|
|
||||||
// check
|
assert.Empty(t, s.GetLeases(LeasesStatic))
|
||||||
ls = s.GetLeases(LeasesStatic)
|
|
||||||
assert.Empty(t, ls)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) {
|
func TestV6_AddReplace(t *testing.T) {
|
||||||
conf := V6ServerConf{
|
sIface, err := v6Create(V6ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.ParseIP("2001::1"),
|
RangeStart: net.ParseIP("2001::1"),
|
||||||
notify: notify6,
|
notify: notify6,
|
||||||
}
|
})
|
||||||
sIface, err := v6Create(conf)
|
require.Nil(t, err)
|
||||||
s, ok := sIface.(*v6Server)
|
s, ok := sIface.(*v6Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// add dynamic lease
|
// Add dynamic leases.
|
||||||
ld := Lease{}
|
dynLeases := []*Lease{{
|
||||||
ld.IP = net.ParseIP("2001::1")
|
IP: net.ParseIP("2001::1"),
|
||||||
ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa")
|
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
s.addLease(&ld)
|
}, {
|
||||||
|
IP: net.ParseIP("2001::2"),
|
||||||
|
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}}
|
||||||
|
|
||||||
// add dynamic lease
|
for _, l := range dynLeases {
|
||||||
{
|
s.addLease(l)
|
||||||
ld := Lease{}
|
|
||||||
ld.IP = net.ParseIP("2001::2")
|
|
||||||
ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
|
||||||
s.addLease(&ld)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add static lease with the same IP
|
stLeases := []Lease{{
|
||||||
l := Lease{}
|
IP: net.ParseIP("2001::1"),
|
||||||
l.IP = net.ParseIP("2001::1")
|
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa")
|
}, {
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
IP: net.ParseIP("2001::3"),
|
||||||
|
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}}
|
||||||
|
|
||||||
// add static lease with the same MAC
|
for _, l := range stLeases {
|
||||||
l = Lease{}
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
l.IP = net.ParseIP("2001::3")
|
}
|
||||||
l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
|
||||||
|
|
||||||
// check
|
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Len(t, ls, 2)
|
require.Len(t, ls, 2)
|
||||||
|
|
||||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
for i, l := range ls {
|
||||||
assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.True(t, stLeases[i].IP.Equal(l.IP))
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
||||||
|
assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
|
||||||
assert.Equal(t, "2001::3", ls[1].IP.String())
|
}
|
||||||
assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String())
|
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV6GetLease(t *testing.T) {
|
func TestV6GetLease(t *testing.T) {
|
||||||
conf := V6ServerConf{
|
var err error
|
||||||
|
sIface, err := v6Create(V6ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.ParseIP("2001::1"),
|
RangeStart: net.ParseIP("2001::1"),
|
||||||
notify: notify6,
|
notify: notify6,
|
||||||
}
|
})
|
||||||
sIface, err := v6Create(conf)
|
require.Nil(t, err)
|
||||||
s, ok := sIface.(*v6Server)
|
s, ok := sIface.(*v6Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")}
|
dnsAddr := net.ParseIP("2000::1")
|
||||||
|
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
|
||||||
s.sid = dhcpv6.Duid{
|
s.sid = dhcpv6.Duid{
|
||||||
Type: dhcpv6.DUID_LLT,
|
Type: dhcpv6.DUID_LLT,
|
||||||
HwType: iana.HWTypeEthernet,
|
HwType: iana.HWTypeEthernet,
|
||||||
|
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
}
|
}
|
||||||
s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
|
|
||||||
l := Lease{}
|
l := Lease{
|
||||||
l.IP = net.ParseIP("2001::1")
|
IP: net.ParseIP("2001::1"),
|
||||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
assert.Nil(t, s.AddStaticLease(l))
|
}
|
||||||
|
require.Nil(t, s.AddStaticLease(l))
|
||||||
|
|
||||||
// "Solicit"
|
var req, resp, msg *dhcpv6.Message
|
||||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
req, _ := dhcpv6.NewSolicit(mac)
|
t.Run("solicit", func(t *testing.T) {
|
||||||
msg, _ := req.GetInnerMessage()
|
req, err = dhcpv6.NewSolicit(mac)
|
||||||
resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg)
|
require.Nil(t, err)
|
||||||
assert.True(t, s.process(msg, req, resp))
|
|
||||||
|
msg, err = req.GetInnerMessage()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, s.process(msg, req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
||||||
|
|
||||||
// check "Advertise"
|
var oia *dhcpv6.OptIANA
|
||||||
assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
var oiaAddr *dhcpv6.OptIAAddress
|
||||||
oia := resp.Options.OneIANA()
|
t.Run("advertise", func(t *testing.T) {
|
||||||
oiaAddr := oia.Options.OneAddress()
|
require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||||
assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String())
|
oia = resp.Options.OneIANA()
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
oiaAddr = oia.Options.OneAddress()
|
||||||
|
|
||||||
// "Request"
|
assert.Equal(t, l.IP, oiaAddr.IPv6Addr)
|
||||||
req, _ = dhcpv6.NewRequestFromAdvertise(resp)
|
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||||
msg, _ = req.GetInnerMessage()
|
})
|
||||||
resp, _ = dhcpv6.NewReplyFromMessage(msg)
|
|
||||||
assert.True(t, s.process(msg, req, resp))
|
|
||||||
|
|
||||||
// check "Reply"
|
t.Run("request", func(t *testing.T) {
|
||||||
assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
req, err = dhcpv6.NewRequestFromAdvertise(resp)
|
||||||
oia = resp.Options.OneIANA()
|
require.Nil(t, err)
|
||||||
oiaAddr = oia.Options.OneAddress()
|
|
||||||
assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String())
|
msg, err = req.GetInnerMessage()
|
||||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, s.process(msg, req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("reply", func(t *testing.T) {
|
||||||
|
require.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||||
|
oia = resp.Options.OneIANA()
|
||||||
|
oiaAddr = oia.Options.OneAddress()
|
||||||
|
|
||||||
|
assert.Equal(t, l.IP, oiaAddr.IPv6Addr)
|
||||||
|
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||||
|
})
|
||||||
|
|
||||||
dnsAddrs := resp.Options.DNS()
|
dnsAddrs := resp.Options.DNS()
|
||||||
assert.Len(t, dnsAddrs, 1)
|
require.Len(t, dnsAddrs, 1)
|
||||||
assert.Equal(t, "2000::1", dnsAddrs[0].String())
|
assert.Equal(t, dnsAddr, dnsAddrs[0])
|
||||||
|
|
||||||
// check lease
|
t.Run("lease", func(t *testing.T) {
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Len(t, ls, 1)
|
require.Len(t, ls, 1)
|
||||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
assert.Equal(t, l.IP, ls[0].IP)
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV6GetDynamicLease(t *testing.T) {
|
func TestV6GetDynamicLease(t *testing.T) {
|
||||||
conf := V6ServerConf{
|
sIface, err := v6Create(V6ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RangeStart: net.ParseIP("2001::2"),
|
RangeStart: net.ParseIP("2001::2"),
|
||||||
notify: notify6,
|
notify: notify6,
|
||||||
}
|
})
|
||||||
sIface, err := v6Create(conf)
|
require.Nil(t, err)
|
||||||
s, ok := sIface.(*v6Server)
|
s, ok := sIface.(*v6Server)
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Nil(t, err)
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")}
|
|
||||||
s.sid = dhcpv6.Duid{
|
|
||||||
Type: dhcpv6.DUID_LLT,
|
|
||||||
HwType: iana.HWTypeEthernet,
|
|
||||||
}
|
|
||||||
s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
|
|
||||||
// "Solicit"
|
dnsAddr := net.ParseIP("2000::1")
|
||||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
|
||||||
req, _ := dhcpv6.NewSolicit(mac)
|
s.sid = dhcpv6.Duid{
|
||||||
msg, _ := req.GetInnerMessage()
|
Type: dhcpv6.DUID_LLT,
|
||||||
resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg)
|
HwType: iana.HWTypeEthernet,
|
||||||
assert.True(t, s.process(msg, req, resp))
|
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
|
}
|
||||||
|
|
||||||
|
var req, resp, msg *dhcpv6.Message
|
||||||
|
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||||
|
t.Run("solicit", func(t *testing.T) {
|
||||||
|
req, err = dhcpv6.NewSolicit(mac)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
msg, err = req.GetInnerMessage()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, s.process(msg, req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
||||||
|
|
||||||
// check "Advertise"
|
var oia *dhcpv6.OptIANA
|
||||||
assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
var oiaAddr *dhcpv6.OptIAAddress
|
||||||
oia := resp.Options.OneIANA()
|
t.Run("advertise", func(t *testing.T) {
|
||||||
oiaAddr := oia.Options.OneAddress()
|
require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
oia = resp.Options.OneIANA()
|
||||||
|
oiaAddr = oia.Options.OneAddress()
|
||||||
|
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||||
|
})
|
||||||
|
|
||||||
// "Request"
|
t.Run("request", func(t *testing.T) {
|
||||||
req, _ = dhcpv6.NewRequestFromAdvertise(resp)
|
req, err = dhcpv6.NewRequestFromAdvertise(resp)
|
||||||
msg, _ = req.GetInnerMessage()
|
require.Nil(t, err)
|
||||||
resp, _ = dhcpv6.NewReplyFromMessage(msg)
|
|
||||||
assert.True(t, s.process(msg, req, resp))
|
|
||||||
|
|
||||||
// check "Reply"
|
msg, err = req.GetInnerMessage()
|
||||||
assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
require.Nil(t, err)
|
||||||
oia = resp.Options.OneIANA()
|
|
||||||
oiaAddr = oia.Options.OneAddress()
|
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, s.process(msg, req, resp))
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("reply", func(t *testing.T) {
|
||||||
|
require.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||||
|
oia = resp.Options.OneIANA()
|
||||||
|
oiaAddr = oia.Options.OneAddress()
|
||||||
|
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||||
|
})
|
||||||
|
|
||||||
dnsAddrs := resp.Options.DNS()
|
dnsAddrs := resp.Options.DNS()
|
||||||
assert.Len(t, dnsAddrs, 1)
|
require.Len(t, dnsAddrs, 1)
|
||||||
assert.Equal(t, "2000::1", dnsAddrs[0].String())
|
assert.Equal(t, dnsAddr, dnsAddrs[0])
|
||||||
|
|
||||||
// check lease
|
t.Run("lease", func(t *testing.T) {
|
||||||
ls := s.GetLeases(LeasesDynamic)
|
ls := s.GetLeases(LeasesDynamic)
|
||||||
assert.Len(t, ls, 1)
|
require.Len(t, ls, 1)
|
||||||
assert.Equal(t, "2001::2", ls[0].IP.String())
|
assert.Equal(t, "2001::2", ls[0].IP.String())
|
||||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
assert.Equal(t, mac, ls[0].HWAddr)
|
||||||
|
})
|
||||||
assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::1")))
|
}
|
||||||
assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2002::2")))
|
|
||||||
assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2")))
|
func TestIP6InRange(t *testing.T) {
|
||||||
assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3")))
|
start := net.ParseIP("2001::2")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
ip net.IP
|
||||||
|
want bool
|
||||||
|
}{{
|
||||||
|
ip: net.ParseIP("2001::1"),
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
ip: net.ParseIP("2002::2"),
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
ip: start,
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
ip: net.ParseIP("2001::3"),
|
||||||
|
want: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.ip.String(), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, ip6InRange(start, tc.ip))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
internal/dnsforward/clientid.go
Normal file
165
internal/dnsforward/clientid.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package dnsforward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxDomainPartLen = 64
|
||||||
|
|
||||||
|
// ValidateClientID returns an error if clientID is not a valid client ID.
|
||||||
|
func ValidateClientID(clientID string) (err error) {
|
||||||
|
if len(clientID) > maxDomainPartLen {
|
||||||
|
return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, r := range clientID {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientIDFromClientServerName extracts and validates a client ID. hostSrvName
|
||||||
|
// is the server name of the host. cliSrvName is the server name as sent by the
|
||||||
|
// client. When strict is true, and client and host server name don't match,
|
||||||
|
// clientIDFromClientServerName will return an error.
|
||||||
|
func clientIDFromClientServerName(hostSrvName, cliSrvName string, strict bool) (clientID string, err error) {
|
||||||
|
if hostSrvName == cliSrvName {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(cliSrvName, hostSrvName) {
|
||||||
|
if !strict {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
||||||
|
err = ValidateClientID(clientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid client id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processClientIDHTTPS extracts the client's ID from the path of the
|
||||||
|
// client's DNS-over-HTTPS request.
|
||||||
|
func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) {
|
||||||
|
pctx := ctx.proxyCtx
|
||||||
|
r := pctx.HTTPRequest
|
||||||
|
if r == nil {
|
||||||
|
ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
origPath := r.URL.Path
|
||||||
|
parts := strings.Split(path.Clean(origPath), "/")
|
||||||
|
if parts[0] == "" {
|
||||||
|
parts = parts[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 0 || parts[0] != "dns-query" {
|
||||||
|
ctx.err = fmt.Errorf("client id check: invalid path %q", origPath)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID := ""
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
// Just /dns-query, no client ID.
|
||||||
|
return resultCodeSuccess
|
||||||
|
case 2:
|
||||||
|
clientID = parts[1]
|
||||||
|
default:
|
||||||
|
ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ValidateClientID(clientID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.err = fmt.Errorf("client id check: invalid client id: %w", err)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.clientID = clientID
|
||||||
|
|
||||||
|
return resultCodeSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsConn is a narrow interface for *tls.Conn to simplify testing.
|
||||||
|
type tlsConn interface {
|
||||||
|
ConnectionState() (cs tls.ConnectionState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// quicSession is a narrow interface for quic.Session to simplify testing.
|
||||||
|
type quicSession interface {
|
||||||
|
ConnectionState() (cs quic.ConnectionState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processClientID extracts the client's ID from the server name of the client's
|
||||||
|
// DOT or DOQ request or the path of the client's DOH.
|
||||||
|
func processClientID(dctx *dnsContext) (rc resultCode) {
|
||||||
|
pctx := dctx.proxyCtx
|
||||||
|
proto := pctx.Proto
|
||||||
|
if proto == proxy.ProtoHTTPS {
|
||||||
|
return processClientIDHTTPS(dctx)
|
||||||
|
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||||
|
return resultCodeSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
srvConf := dctx.srv.conf
|
||||||
|
hostSrvName := srvConf.TLSConfig.ServerName
|
||||||
|
if hostSrvName == "" {
|
||||||
|
return resultCodeSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
cliSrvName := ""
|
||||||
|
if proto == proxy.ProtoTLS {
|
||||||
|
conn := pctx.Conn
|
||||||
|
tc, ok := conn.(tlsConn)
|
||||||
|
if !ok {
|
||||||
|
dctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
cliSrvName = tc.ConnectionState().ServerName
|
||||||
|
} else if proto == proxy.ProtoQUIC {
|
||||||
|
qs, ok := pctx.QUICSession.(quicSession)
|
||||||
|
if !ok {
|
||||||
|
dctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
cliSrvName = qs.ConnectionState().ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName, srvConf.StrictSNICheck)
|
||||||
|
if err != nil {
|
||||||
|
dctx.err = fmt.Errorf("client id check: %w", err)
|
||||||
|
|
||||||
|
return resultCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
dctx.clientID = clientID
|
||||||
|
|
||||||
|
return resultCodeSuccess
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/lucas-clemente/quic-go"
|
"github.com/lucas-clemente/quic-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testTLSConn is a tlsConn for tests.
|
// testTLSConn is a tlsConn for tests.
|
||||||
@@ -53,6 +54,7 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
wantClientID string
|
wantClientID string
|
||||||
wantErrMsg string
|
wantErrMsg string
|
||||||
wantRes resultCode
|
wantRes resultCode
|
||||||
|
strictSNI bool
|
||||||
}{{
|
}{{
|
||||||
name: "udp",
|
name: "udp",
|
||||||
proto: proxy.ProtoUDP,
|
proto: proxy.ProtoUDP,
|
||||||
@@ -61,6 +63,7 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
wantRes: resultCodeSuccess,
|
wantRes: resultCodeSuccess,
|
||||||
|
strictSNI: false,
|
||||||
}, {
|
}, {
|
||||||
name: "tls_no_client_id",
|
name: "tls_no_client_id",
|
||||||
proto: proxy.ProtoTLS,
|
proto: proxy.ProtoTLS,
|
||||||
@@ -69,6 +72,26 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
wantRes: resultCodeSuccess,
|
wantRes: resultCodeSuccess,
|
||||||
|
strictSNI: true,
|
||||||
|
}, {
|
||||||
|
name: "tls_no_client_server_name",
|
||||||
|
proto: proxy.ProtoTLS,
|
||||||
|
hostSrvName: "example.com",
|
||||||
|
cliSrvName: "",
|
||||||
|
wantClientID: "",
|
||||||
|
wantErrMsg: `client id check: client server name "" ` +
|
||||||
|
`doesn't match host server name "example.com"`,
|
||||||
|
wantRes: resultCodeError,
|
||||||
|
strictSNI: true,
|
||||||
|
}, {
|
||||||
|
name: "tls_no_client_server_name_no_strict",
|
||||||
|
proto: proxy.ProtoTLS,
|
||||||
|
hostSrvName: "example.com",
|
||||||
|
cliSrvName: "",
|
||||||
|
wantClientID: "",
|
||||||
|
wantErrMsg: "",
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
strictSNI: false,
|
||||||
}, {
|
}, {
|
||||||
name: "tls_client_id",
|
name: "tls_client_id",
|
||||||
proto: proxy.ProtoTLS,
|
proto: proxy.ProtoTLS,
|
||||||
@@ -77,30 +100,39 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
wantClientID: "cli",
|
wantClientID: "cli",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
wantRes: resultCodeSuccess,
|
wantRes: resultCodeSuccess,
|
||||||
|
strictSNI: true,
|
||||||
}, {
|
}, {
|
||||||
name: "tls_client_id_hostname_error",
|
name: "tls_client_id_hostname_error",
|
||||||
proto: proxy.ProtoTLS,
|
proto: proxy.ProtoTLS,
|
||||||
hostSrvName: "example.com",
|
hostSrvName: "example.com",
|
||||||
cliSrvName: "cli.example.net",
|
cliSrvName: "cli.example.net",
|
||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: `client id check: client server name "cli.example.net" doesn't match host server name "example.com"`,
|
wantErrMsg: `client id check: client server name "cli.example.net" ` +
|
||||||
wantRes: resultCodeError,
|
`doesn't match host server name "example.com"`,
|
||||||
|
wantRes: resultCodeError,
|
||||||
|
strictSNI: true,
|
||||||
}, {
|
}, {
|
||||||
name: "tls_invalid_client_id",
|
name: "tls_invalid_client_id",
|
||||||
proto: proxy.ProtoTLS,
|
proto: proxy.ProtoTLS,
|
||||||
hostSrvName: "example.com",
|
hostSrvName: "example.com",
|
||||||
cliSrvName: "!!!.example.com",
|
cliSrvName: "!!!.example.com",
|
||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`,
|
wantErrMsg: `client id check: invalid client id: invalid char '!' ` +
|
||||||
wantRes: resultCodeError,
|
`at index 0 in client id "!!!"`,
|
||||||
|
wantRes: resultCodeError,
|
||||||
|
strictSNI: true,
|
||||||
}, {
|
}, {
|
||||||
name: "tls_client_id_too_long",
|
name: "tls_client_id_too_long",
|
||||||
proto: proxy.ProtoTLS,
|
proto: proxy.ProtoTLS,
|
||||||
hostSrvName: "example.com",
|
hostSrvName: "example.com",
|
||||||
cliSrvName: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789.example.com",
|
cliSrvName: `abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno` +
|
||||||
|
`pqrstuvwxyz0123456789.example.com`,
|
||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" is too long, max: 64`,
|
wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmno` +
|
||||||
wantRes: resultCodeError,
|
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" ` +
|
||||||
|
`is too long, max: 64`,
|
||||||
|
wantRes: resultCodeError,
|
||||||
|
strictSNI: true,
|
||||||
}, {
|
}, {
|
||||||
name: "quic_client_id",
|
name: "quic_client_id",
|
||||||
proto: proxy.ProtoQUIC,
|
proto: proxy.ProtoQUIC,
|
||||||
@@ -109,14 +141,17 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
wantClientID: "cli",
|
wantClientID: "cli",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
wantRes: resultCodeSuccess,
|
wantRes: resultCodeSuccess,
|
||||||
|
strictSNI: true,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tlsConf := TLSConfig{
|
||||||
|
ServerName: tc.hostSrvName,
|
||||||
|
StrictSNICheck: tc.strictSNI,
|
||||||
|
}
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
conf: ServerConfig{
|
conf: ServerConfig{TLSConfig: tlsConf},
|
||||||
TLSConfig: TLSConfig{ServerName: tc.hostSrvName},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
@@ -146,10 +181,11 @@ func TestProcessClientID(t *testing.T) {
|
|||||||
assert.Equal(t, tc.wantRes, res)
|
assert.Equal(t, tc.wantRes, res)
|
||||||
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
||||||
|
|
||||||
if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) {
|
if tc.wantErrMsg == "" {
|
||||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
|
||||||
} else {
|
|
||||||
assert.Nil(t, dctx.err)
|
assert.Nil(t, dctx.err)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, dctx.err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -202,8 +238,9 @@ func TestProcessClientID_https(t *testing.T) {
|
|||||||
name: "invalid_client_id",
|
name: "invalid_client_id",
|
||||||
path: "/dns-query/!!!",
|
path: "/dns-query/!!!",
|
||||||
wantClientID: "",
|
wantClientID: "",
|
||||||
wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`,
|
wantErrMsg: `client id check: invalid client id: invalid char '!'` +
|
||||||
wantRes: resultCodeError,
|
` at index 0 in client id "!!!"`,
|
||||||
|
wantRes: resultCodeError,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -225,10 +262,11 @@ func TestProcessClientID_https(t *testing.T) {
|
|||||||
assert.Equal(t, tc.wantRes, res)
|
assert.Equal(t, tc.wantRes, res)
|
||||||
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
||||||
|
|
||||||
if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) {
|
if tc.wantErrMsg == "" {
|
||||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
|
||||||
} else {
|
|
||||||
assert.Nil(t, dctx.err)
|
assert.Nil(t, dctx.err)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, dctx.err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -282,7 +282,7 @@ func (s *Server) prepareUpstreamSettings() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(upstreamConfig.Upstreams) == 0 {
|
if len(upstreamConfig.Upstreams) == 0 {
|
||||||
log.Info("Warning: no default upstream servers specified, using %v", defaultDNS)
|
log.Info("warning: no default upstream servers specified, using %v", defaultDNS)
|
||||||
uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dns: failed to parse default upstreams: %v", err)
|
return fmt.Errorf("dns: failed to parse default upstreams: %v", err)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,7 +10,6 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/lucas-clemente/quic-go"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -234,154 +230,6 @@ func processInternalHosts(ctx *dnsContext) (rc resultCode) {
|
|||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxDomainPartLen = 64
|
|
||||||
|
|
||||||
// ValidateClientID returns an error if clientID is not a valid client ID.
|
|
||||||
func ValidateClientID(clientID string) (err error) {
|
|
||||||
if len(clientID) > maxDomainPartLen {
|
|
||||||
return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, r := range clientID {
|
|
||||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientIDFromClientServerName extracts and validates a client ID. hostSrvName
|
|
||||||
// is the server name of the host. cliSrvName is the server name as sent by the
|
|
||||||
// client.
|
|
||||||
func clientIDFromClientServerName(hostSrvName, cliSrvName string) (clientID string, err error) {
|
|
||||||
if hostSrvName == cliSrvName {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasSuffix(cliSrvName, hostSrvName) {
|
|
||||||
return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
|
||||||
err = ValidateClientID(clientID)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("invalid client id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// processClientIDHTTPS extracts the client's ID from the path of the
|
|
||||||
// client's DNS-over-HTTPS request.
|
|
||||||
func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) {
|
|
||||||
pctx := ctx.proxyCtx
|
|
||||||
r := pctx.HTTPRequest
|
|
||||||
if r == nil {
|
|
||||||
ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
origPath := r.URL.Path
|
|
||||||
parts := strings.Split(path.Clean(origPath), "/")
|
|
||||||
if parts[0] == "" {
|
|
||||||
parts = parts[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) == 0 || parts[0] != "dns-query" {
|
|
||||||
ctx.err = fmt.Errorf("client id check: invalid path %q", origPath)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
clientID := ""
|
|
||||||
switch len(parts) {
|
|
||||||
case 1:
|
|
||||||
// Just /dns-query, no client ID.
|
|
||||||
return resultCodeSuccess
|
|
||||||
case 2:
|
|
||||||
clientID = parts[1]
|
|
||||||
default:
|
|
||||||
ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
err := ValidateClientID(clientID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.err = fmt.Errorf("client id check: invalid client id: %w", err)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.clientID = clientID
|
|
||||||
|
|
||||||
return resultCodeSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlsConn is a narrow interface for *tls.Conn to simplify testing.
|
|
||||||
type tlsConn interface {
|
|
||||||
ConnectionState() (cs tls.ConnectionState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// quicSession is a narrow interface for quic.Session to simplify testing.
|
|
||||||
type quicSession interface {
|
|
||||||
ConnectionState() (cs quic.ConnectionState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// processClientID extracts the client's ID from the server name of the client's
|
|
||||||
// DOT or DOQ request or the path of the client's DOH.
|
|
||||||
func processClientID(ctx *dnsContext) (rc resultCode) {
|
|
||||||
pctx := ctx.proxyCtx
|
|
||||||
proto := pctx.Proto
|
|
||||||
if proto == proxy.ProtoHTTPS {
|
|
||||||
return processClientIDHTTPS(ctx)
|
|
||||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
|
||||||
return resultCodeSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
hostSrvName := ctx.srv.conf.TLSConfig.ServerName
|
|
||||||
if hostSrvName == "" {
|
|
||||||
return resultCodeSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
cliSrvName := ""
|
|
||||||
if proto == proxy.ProtoTLS {
|
|
||||||
conn := pctx.Conn
|
|
||||||
tc, ok := conn.(tlsConn)
|
|
||||||
if !ok {
|
|
||||||
ctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
cliSrvName = tc.ConnectionState().ServerName
|
|
||||||
} else if proto == proxy.ProtoQUIC {
|
|
||||||
qs, ok := pctx.QUICSession.(quicSession)
|
|
||||||
if !ok {
|
|
||||||
ctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
cliSrvName = qs.ConnectionState().ServerName
|
|
||||||
}
|
|
||||||
|
|
||||||
clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName)
|
|
||||||
if err != nil {
|
|
||||||
ctx.err = fmt.Errorf("client id check: %w", err)
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.clientID = clientID
|
|
||||||
|
|
||||||
return resultCodeSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
// Respond to PTR requests if the target IP address is leased by our DHCP server
|
// Respond to PTR requests if the target IP address is leased by our DHCP server
|
||||||
func processInternalIPAddrs(ctx *dnsContext) (rc resultCode) {
|
func processInternalIPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||||
s := ctx.srv
|
s := ctx.srv
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/utils"
|
"github.com/AdguardTeam/golibs/utils"
|
||||||
@@ -314,6 +315,11 @@ func ValidateUpstreams(upstreams []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err := proxy.ParseUpstreamsConfig(upstreams, []string{}, DefaultTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var defaultUpstreamFound bool
|
var defaultUpstreamFound bool
|
||||||
for _, u := range upstreams {
|
for _, u := range upstreams {
|
||||||
d, err := validateUpstream(u)
|
d, err := validateUpstream(u)
|
||||||
|
|||||||
@@ -251,12 +251,15 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
|
|||||||
|
|
||||||
// Allow the frontend from the HTTP origin to send requests to the HTTPS
|
// Allow the frontend from the HTTP origin to send requests to the HTTPS
|
||||||
// server. This can happen when the user has just set up HTTPS with
|
// server. This can happen when the user has just set up HTTPS with
|
||||||
// redirects.
|
// redirects. Prevent cache-related errors by setting the Vary header.
|
||||||
|
//
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
|
||||||
originURL := &url.URL{
|
originURL := &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: r.Host,
|
Host: r.Host,
|
||||||
}
|
}
|
||||||
w.Header().Set("Access-Control-Allow-Origin", originURL.String())
|
w.Header().Set("Access-Control-Allow-Origin", originURL.String())
|
||||||
|
w.Header().Set("Vary", "Origin")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,15 +295,20 @@ func run(args options) {
|
|||||||
BindPort: config.BindPort,
|
BindPort: config.BindPort,
|
||||||
BetaBindPort: config.BetaBindPort,
|
BetaBindPort: config.BetaBindPort,
|
||||||
|
|
||||||
ReadTimeout: ReadTimeout,
|
ReadTimeout: readTimeout,
|
||||||
ReadHeaderTimeout: ReadHeaderTimeout,
|
ReadHeaderTimeout: readHdrTimeout,
|
||||||
WriteTimeout: WriteTimeout,
|
WriteTimeout: writeTimeout,
|
||||||
}
|
}
|
||||||
Context.web = CreateWeb(&webConf)
|
Context.web = CreateWeb(&webConf)
|
||||||
if Context.web == nil {
|
if Context.web == nil {
|
||||||
log.Fatalf("Can't initialize Web module")
|
log.Fatalf("Can't initialize Web module")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context.ipDetector, err = newIPDetector()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
err := initDNSServer()
|
err := initDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -315,6 +320,7 @@ func run(args options) {
|
|||||||
go func() {
|
go func() {
|
||||||
err := startDNSServer()
|
err := startDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
closeDNSServer()
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -324,11 +330,6 @@ func run(args options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.ipDetector, err = newIPDetector()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
Context.web.Start()
|
Context.web.Start()
|
||||||
|
|
||||||
// wait indefinitely for other go-routines to complete their job
|
// wait indefinitely for other go-routines to complete their job
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||||
var ipd *ipDetector
|
var ipd *ipDetector
|
||||||
|
var err error
|
||||||
|
|
||||||
t.Run("newIPDetector", func(t *testing.T) {
|
ipd, err = newIPDetector()
|
||||||
var err error
|
require.Nil(t, err)
|
||||||
ipd, err = newIPDetector()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -22,15 +22,43 @@ func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Ha
|
|||||||
return wrapped
|
return wrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestBodySizeLimit is maximum request body length in bytes.
|
// defaultReqBodySzLim is the default maximum request body size.
|
||||||
const RequestBodySizeLimit = 64 * 1024
|
const defaultReqBodySzLim = 64 * 1024
|
||||||
|
|
||||||
|
// largerReqBodySzLim is the maximum request body size for APIs expecting larger
|
||||||
|
// requests.
|
||||||
|
const largerReqBodySzLim = 4 * 1024 * 1024
|
||||||
|
|
||||||
|
// expectsLargerRequests shows if this request should use a larger body size
|
||||||
|
// limit. These are exceptions for poorly designed current APIs as well as APIs
|
||||||
|
// that are designed to expect large files and requests. Remove once the new,
|
||||||
|
// better APIs are up.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2666 and
|
||||||
|
// https://github.com/AdguardTeam/AdGuardHome/issues/2675.
|
||||||
|
func expectsLargerRequests(r *http.Request) (ok bool) {
|
||||||
|
m := r.Method
|
||||||
|
if m != http.MethodPost {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p := r.URL.Path
|
||||||
|
return p == "/control/access/set" ||
|
||||||
|
p == "/control/filtering/set_rules"
|
||||||
|
}
|
||||||
|
|
||||||
// limitRequestBody wraps underlying handler h, making it's request's body Read
|
// limitRequestBody wraps underlying handler h, making it's request's body Read
|
||||||
// method limited.
|
// method limited.
|
||||||
func limitRequestBody(h http.Handler) (limited http.Handler) {
|
func limitRequestBody(h http.Handler) (limited http.Handler) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit)
|
|
||||||
|
var szLim int64 = defaultReqBodySzLim
|
||||||
|
if expectsLargerRequests(r) {
|
||||||
|
szLim = largerReqBodySzLim
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Body, err = aghio.LimitReadCloser(r.Body, szLim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("limitRequestBody: %s", err)
|
log.Error("limitRequestBody: %s", err)
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLimitRequestBody(t *testing.T) {
|
func TestLimitRequestBody(t *testing.T) {
|
||||||
errReqLimitReached := &aghio.LimitReachedError{
|
errReqLimitReached := &aghio.LimitReachedError{
|
||||||
Limit: RequestBodySizeLimit,
|
Limit: defaultReqBodySzLim,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -28,8 +29,8 @@ func TestLimitRequestBody(t *testing.T) {
|
|||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "so_big",
|
name: "so_big",
|
||||||
body: string(make([]byte, RequestBodySizeLimit+1)),
|
body: string(make([]byte, defaultReqBodySzLim+1)),
|
||||||
want: make([]byte, RequestBodySizeLimit),
|
want: make([]byte, defaultReqBodySzLim),
|
||||||
wantErr: errReqLimitReached,
|
wantErr: errReqLimitReached,
|
||||||
}, {
|
}, {
|
||||||
name: "empty",
|
name: "empty",
|
||||||
@@ -60,8 +61,8 @@ func TestLimitRequestBody(t *testing.T) {
|
|||||||
|
|
||||||
lim.ServeHTTP(res, req)
|
lim.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
require.Equal(t, tc.wantErr, err)
|
||||||
assert.Equal(t, tc.want, res.Body.Bytes())
|
assert.Equal(t, tc.want, res.Body.Bytes())
|
||||||
assert.Equal(t, tc.wantErr, err)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,183 +3,108 @@ package home
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpgrade1to2(t *testing.T) {
|
// any is a convenient alias for interface{}.
|
||||||
// let's create test config for 1 schema version
|
type any = interface{}
|
||||||
diskConfig := createTestDiskConfig(1)
|
|
||||||
|
|
||||||
// update config
|
// object is a convenient alias for map[string]interface{}.
|
||||||
err := upgradeSchema1to2(&diskConfig)
|
type object = map[string]any
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Can't upgrade schema version from 1 to 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that schema version was bumped
|
func TestUpgradeSchema1to2(t *testing.T) {
|
||||||
compareSchemaVersion(t, diskConfig["schema_version"], 2)
|
diskConf := testDiskConf(1)
|
||||||
|
|
||||||
// old coredns entry should be removed
|
err := upgradeSchema1to2(&diskConf)
|
||||||
_, ok := diskConfig["coredns"]
|
require.Nil(t, err)
|
||||||
if ok {
|
|
||||||
t.Fatalf("Core DNS config was not removed after upgrade schema version from 1 to 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// pull out new dns config
|
require.Equal(t, diskConf["schema_version"], 2)
|
||||||
dnsMap, ok := diskConfig["dns"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("No DNS config after upgrade schema version from 1 to 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cast dns configurations to maps and compare them
|
_, ok := diskConf["coredns"]
|
||||||
oldDNSConfig := castInterfaceToMap(t, createTestDNSConfig(1))
|
require.False(t, ok)
|
||||||
newDNSConfig := castInterfaceToMap(t, dnsMap)
|
|
||||||
compareConfigs(t, &oldDNSConfig, &newDNSConfig)
|
dnsMap, ok := diskConf["dns"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
oldDNSConf := convertToObject(t, testDNSConf(1))
|
||||||
|
newDNSConf := convertToObject(t, dnsMap)
|
||||||
|
assert.Equal(t, oldDNSConf, newDNSConf)
|
||||||
|
|
||||||
// exclude dns config and schema version from disk config comparison
|
|
||||||
oldExcludedEntries := []string{"coredns", "schema_version"}
|
oldExcludedEntries := []string{"coredns", "schema_version"}
|
||||||
newExcludedEntries := []string{"dns", "schema_version"}
|
newExcludedEntries := []string{"dns", "schema_version"}
|
||||||
oldDiskConfig := createTestDiskConfig(1)
|
oldDiskConf := testDiskConf(1)
|
||||||
compareConfigsWithoutEntries(t, &oldDiskConfig, &diskConfig, oldExcludedEntries, newExcludedEntries)
|
assertEqualExcept(t, oldDiskConf, diskConf, oldExcludedEntries, newExcludedEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpgrade2to3(t *testing.T) {
|
func TestUpgradeSchema2to3(t *testing.T) {
|
||||||
// let's create test config
|
diskConf := testDiskConf(2)
|
||||||
diskConfig := createTestDiskConfig(2)
|
|
||||||
|
|
||||||
// upgrade schema from 2 to 3
|
err := upgradeSchema2to3(&diskConf)
|
||||||
err := upgradeSchema2to3(&diskConfig)
|
require.Nil(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Can't update schema version from 2 to 3: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check new schema version
|
require.Equal(t, diskConf["schema_version"], 3)
|
||||||
compareSchemaVersion(t, diskConfig["schema_version"], 3)
|
|
||||||
|
|
||||||
// pull out new dns configuration
|
dnsMap, ok := diskConf["dns"]
|
||||||
dnsMap, ok := diskConfig["dns"]
|
require.True(t, ok)
|
||||||
if !ok {
|
|
||||||
t.Fatalf("No dns config in new configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cast dns configuration to map
|
newDNSConf := convertToObject(t, dnsMap)
|
||||||
newDNSConfig := castInterfaceToMap(t, dnsMap)
|
bootstrapDNS := newDNSConf["bootstrap_dns"]
|
||||||
|
|
||||||
// check if bootstrap DNS becomes an array
|
|
||||||
bootstrapDNS := newDNSConfig["bootstrap_dns"]
|
|
||||||
switch v := bootstrapDNS.(type) {
|
switch v := bootstrapDNS.(type) {
|
||||||
case []string:
|
case []string:
|
||||||
if len(v) != 1 {
|
require.Len(t, v, 1)
|
||||||
t.Fatalf("Wrong count of bootsrap DNS servers: %d", len(v))
|
require.Equal(t, "8.8.8.8:53", v[0])
|
||||||
}
|
|
||||||
|
|
||||||
if v[0] != "8.8.8.8:53" {
|
|
||||||
t.Fatalf("Bootsrap DNS server is not 8.8.8.8:53 : %s", v[0])
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
t.Fatalf("Wrong type for bootsrap DNS: %T", v)
|
t.Fatalf("wrong type for bootsrap dns: %T", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exclude bootstrap DNS from DNS configs comparison
|
|
||||||
excludedEntries := []string{"bootstrap_dns"}
|
excludedEntries := []string{"bootstrap_dns"}
|
||||||
oldDNSConfig := castInterfaceToMap(t, createTestDNSConfig(2))
|
oldDNSConf := convertToObject(t, testDNSConf(2))
|
||||||
compareConfigsWithoutEntries(t, &oldDNSConfig, &newDNSConfig, excludedEntries, excludedEntries)
|
assertEqualExcept(t, oldDNSConf, newDNSConf, excludedEntries, excludedEntries)
|
||||||
|
|
||||||
// excluded dns config and schema version from disk config comparison
|
|
||||||
excludedEntries = []string{"dns", "schema_version"}
|
excludedEntries = []string{"dns", "schema_version"}
|
||||||
oldDiskConfig := createTestDiskConfig(2)
|
oldDiskConf := testDiskConf(2)
|
||||||
compareConfigsWithoutEntries(t, &oldDiskConfig, &diskConfig, excludedEntries, excludedEntries)
|
assertEqualExcept(t, oldDiskConf, diskConf, excludedEntries, excludedEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func castInterfaceToMap(t *testing.T, oldConfig interface{}) (newConfig map[string]interface{}) {
|
func convertToObject(t *testing.T, oldConf any) (newConf object) {
|
||||||
newConfig = make(map[string]interface{})
|
t.Helper()
|
||||||
switch v := oldConfig.(type) {
|
|
||||||
case map[interface{}]interface{}:
|
switch v := oldConf.(type) {
|
||||||
|
case map[any]any:
|
||||||
|
newConf = make(object, len(v))
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
newConfig[fmt.Sprint(key)] = value
|
newConf[fmt.Sprint(key)] = value
|
||||||
}
|
}
|
||||||
case map[string]interface{}:
|
case object:
|
||||||
|
newConf = make(object, len(v))
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
newConfig[key] = value
|
newConf[key] = value
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("DNS configuration is not a map")
|
t.Fatalf("dns configuration is not a map, got %T", oldConf)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return newConf
|
||||||
}
|
}
|
||||||
|
|
||||||
// compareConfigsWithoutEntry removes entries from configs and returns result of compareConfigs
|
// assertEqualExcept removes entries from configs and compares them.
|
||||||
func compareConfigsWithoutEntries(t *testing.T, oldConfig, newConfig *map[string]interface{}, oldKey, newKey []string) {
|
func assertEqualExcept(t *testing.T, oldConf, newConf object, oldKeys, newKeys []string) {
|
||||||
for _, k := range oldKey {
|
t.Helper()
|
||||||
delete(*oldConfig, k)
|
|
||||||
|
for _, k := range oldKeys {
|
||||||
|
delete(oldConf, k)
|
||||||
}
|
}
|
||||||
for _, k := range newKey {
|
for _, k := range newKeys {
|
||||||
delete(*newConfig, k)
|
delete(newConf, k)
|
||||||
}
|
}
|
||||||
compareConfigs(t, oldConfig, newConfig)
|
|
||||||
|
assert.Equal(t, oldConf, newConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compares configs before and after schema upgrade
|
func testDiskConf(schemaVersion int) (diskConf object) {
|
||||||
func compareConfigs(t *testing.T, oldConfig, newConfig *map[string]interface{}) {
|
filters := []filter{
|
||||||
if len(*oldConfig) != len(*newConfig) {
|
|
||||||
t.Fatalf("wrong config entries count! Before upgrade: %d; After upgrade: %d", len(*oldConfig), len(*oldConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check old and new entries
|
|
||||||
for k, v := range *newConfig {
|
|
||||||
switch value := v.(type) {
|
|
||||||
case string:
|
|
||||||
if value != (*oldConfig)[k] {
|
|
||||||
t.Fatalf("wrong value for string %s. Before update: %s; After update: %s", k, (*oldConfig)[k], value)
|
|
||||||
}
|
|
||||||
case int:
|
|
||||||
if value != (*oldConfig)[k] {
|
|
||||||
t.Fatalf("wrong value for int %s. Before update: %d; After update: %d", k, (*oldConfig)[k], value)
|
|
||||||
}
|
|
||||||
case []string:
|
|
||||||
for i, line := range value {
|
|
||||||
if len((*oldConfig)[k].([]string)) != len(value) {
|
|
||||||
t.Fatalf("wrong array length for %s. Before update: %d; After update: %d", k, len((*oldConfig)[k].([]string)), len(value))
|
|
||||||
}
|
|
||||||
if (*oldConfig)[k].([]string)[i] != line {
|
|
||||||
t.Fatalf("wrong data for string array %s. Before update: %s; After update: %s", k, (*oldConfig)[k].([]string)[i], line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
if v != (*oldConfig)[k].(bool) {
|
|
||||||
t.Fatalf("wrong boolean value for %s", k)
|
|
||||||
}
|
|
||||||
case []filter:
|
|
||||||
if len((*oldConfig)[k].([]filter)) != len(value) {
|
|
||||||
t.Fatalf("wrong filters count. Before update: %d; After update: %d", len((*oldConfig)[k].([]filter)), len(value))
|
|
||||||
}
|
|
||||||
for i, newFilter := range value {
|
|
||||||
oldFilter := (*oldConfig)[k].([]filter)[i]
|
|
||||||
if oldFilter.Enabled != newFilter.Enabled || oldFilter.Name != newFilter.Name || oldFilter.RulesCount != newFilter.RulesCount {
|
|
||||||
t.Fatalf("old filter %s not equals new filter %s", oldFilter.Name, newFilter.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Fatalf("uknown data type for %s: %T", k, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compareSchemaVersion check if newSchemaVersion equals schemaVersion
|
|
||||||
func compareSchemaVersion(t *testing.T, newSchemaVersion interface{}, schemaVersion int) {
|
|
||||||
switch v := newSchemaVersion.(type) {
|
|
||||||
case int:
|
|
||||||
if v != schemaVersion {
|
|
||||||
t.Fatalf("Wrong schema version in new config file")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Fatalf("Schema version is not an integer after update")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestDiskConfig(schemaVersion int) (diskConfig map[string]interface{}) {
|
|
||||||
diskConfig = make(map[string]interface{})
|
|
||||||
diskConfig["language"] = "en"
|
|
||||||
diskConfig["filters"] = []filter{
|
|
||||||
{
|
{
|
||||||
URL: "https://filters.adtidy.org/android/filters/111_optimized.txt",
|
URL: "https://filters.adtidy.org/android/filters/111_optimized.txt",
|
||||||
Name: "Latvian filter",
|
Name: "Latvian filter",
|
||||||
@@ -191,40 +116,51 @@ func createTestDiskConfig(schemaVersion int) (diskConfig map[string]interface{})
|
|||||||
RulesCount: 200,
|
RulesCount: 200,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
diskConfig["user_rules"] = []string{}
|
diskConf = object{
|
||||||
diskConfig["schema_version"] = schemaVersion
|
"language": "en",
|
||||||
diskConfig["bind_host"] = "0.0.0.0"
|
"filters": filters,
|
||||||
diskConfig["bind_port"] = 80
|
"user_rules": []string{},
|
||||||
diskConfig["auth_name"] = "name"
|
"schema_version": schemaVersion,
|
||||||
diskConfig["auth_pass"] = "pass"
|
"bind_host": "0.0.0.0",
|
||||||
dnsConfig := createTestDNSConfig(schemaVersion)
|
"bind_port": 80,
|
||||||
if schemaVersion > 1 {
|
"auth_name": "name",
|
||||||
diskConfig["dns"] = dnsConfig
|
"auth_pass": "pass",
|
||||||
} else {
|
|
||||||
diskConfig["coredns"] = dnsConfig
|
|
||||||
}
|
}
|
||||||
return diskConfig
|
|
||||||
|
dnsConf := testDNSConf(schemaVersion)
|
||||||
|
if schemaVersion > 1 {
|
||||||
|
diskConf["dns"] = dnsConf
|
||||||
|
} else {
|
||||||
|
diskConf["coredns"] = dnsConf
|
||||||
|
}
|
||||||
|
|
||||||
|
return diskConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestDNSConfig(schemaVersion int) map[interface{}]interface{} {
|
// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would
|
||||||
dnsConfig := make(map[interface{}]interface{})
|
// unmarshal it. In YAML, keys aren't guaranteed to always only be strings.
|
||||||
dnsConfig["port"] = 53
|
func testDNSConf(schemaVersion int) (dnsConf map[any]any) {
|
||||||
dnsConfig["blocked_response_ttl"] = 10
|
dnsConf = map[any]any{
|
||||||
dnsConfig["querylog_enabled"] = true
|
"port": 53,
|
||||||
dnsConfig["ratelimit"] = 20
|
"blocked_response_ttl": 10,
|
||||||
dnsConfig["bootstrap_dns"] = "8.8.8.8:53"
|
"querylog_enabled": true,
|
||||||
if schemaVersion > 2 {
|
"ratelimit": 20,
|
||||||
dnsConfig["bootstrap_dns"] = []string{"8.8.8.8:53"}
|
"bootstrap_dns": "8.8.8.8:53",
|
||||||
|
"parental_sensitivity": 13,
|
||||||
|
"ratelimit_whitelist": []string{},
|
||||||
|
"upstream_dns": []string{"tls://1.1.1.1", "tls://1.0.0.1", "8.8.8.8"},
|
||||||
|
"filtering_enabled": true,
|
||||||
|
"refuse_any": true,
|
||||||
|
"parental_enabled": true,
|
||||||
|
"bind_host": "0.0.0.0",
|
||||||
|
"protection_enabled": true,
|
||||||
|
"safesearch_enabled": true,
|
||||||
|
"safebrowsing_enabled": true,
|
||||||
}
|
}
|
||||||
dnsConfig["parental_sensitivity"] = 13
|
|
||||||
dnsConfig["ratelimit_whitelist"] = []string{}
|
if schemaVersion > 2 {
|
||||||
dnsConfig["upstream_dns"] = []string{"tls://1.1.1.1", "tls://1.0.0.1", "8.8.8.8"}
|
dnsConf["bootstrap_dns"] = []string{"8.8.8.8:53"}
|
||||||
dnsConfig["filtering_enabled"] = true
|
}
|
||||||
dnsConfig["refuse_any"] = true
|
|
||||||
dnsConfig["parental_enabled"] = true
|
return dnsConf
|
||||||
dnsConfig["bind_host"] = "0.0.0.0"
|
|
||||||
dnsConfig["protection_enabled"] = true
|
|
||||||
dnsConfig["safesearch_enabled"] = true
|
|
||||||
dnsConfig["safebrowsing_enabled"] = true
|
|
||||||
return dnsConfig
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ReadTimeout is the maximum duration for reading the entire request,
|
// readTimeout is the maximum duration for reading the entire request,
|
||||||
// including the body.
|
// including the body.
|
||||||
ReadTimeout = 10 * time.Second
|
readTimeout = 60 * time.Second
|
||||||
|
// readHdrTimeout is the amount of time allowed to read request headers.
|
||||||
// ReadHeaderTimeout is the amount of time allowed to read request
|
readHdrTimeout = 60 * time.Second
|
||||||
// headers.
|
// writeTimeout is the maximum duration before timing out writes of the
|
||||||
ReadHeaderTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
// WriteTimeout is the maximum duration before timing out writes of the
|
|
||||||
// response.
|
// response.
|
||||||
WriteTimeout = 10 * time.Second
|
writeTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type webConfig struct {
|
type webConfig struct {
|
||||||
@@ -191,7 +188,10 @@ func (web *Web) Start() {
|
|||||||
WriteTimeout: web.conf.WriteTimeout,
|
WriteTimeout: web.conf.WriteTimeout,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
errs <- web.httpServerBeta.ListenAndServe()
|
betaErr := web.httpServerBeta.ListenAndServe()
|
||||||
|
if betaErr != nil {
|
||||||
|
log.Error("starting beta http server: %s", betaErr)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ func (web *Web) tlsServerLoop() {
|
|||||||
RootCAs: Context.tlsRoots,
|
RootCAs: Context.tlsRoots,
|
||||||
CipherSuites: Context.tlsCiphers,
|
CipherSuites: Context.tlsCiphers,
|
||||||
},
|
},
|
||||||
Handler: Context.mux,
|
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||||
ReadTimeout: web.conf.ReadTimeout,
|
ReadTimeout: web.conf.ReadTimeout,
|
||||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||||
WriteTimeout: web.conf.WriteTimeout,
|
WriteTimeout: web.conf.WriteTimeout,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func NewClientProto(s string) (cp ClientProto, err error) {
|
|||||||
ClientProtoDOH,
|
ClientProtoDOH,
|
||||||
ClientProtoDOQ,
|
ClientProtoDOQ,
|
||||||
ClientProtoDOT,
|
ClientProtoDOT,
|
||||||
|
ClientProtoDNSCrypt,
|
||||||
ClientProtoPlain:
|
ClientProtoPlain:
|
||||||
|
|
||||||
return cp, nil
|
return cp, nil
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package querylog
|
package querylog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,226 +14,260 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
aghtest.DiscardLogOutput(m)
|
aghtest.DiscardLogOutput(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestDir() string {
|
// TestQueryLog tests adding and loading (with filtering) entries from disk and
|
||||||
const dir = "./agh-test"
|
// memory.
|
||||||
_ = os.RemoveAll(dir)
|
|
||||||
_ = os.MkdirAll(dir, 0o755)
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check adding and loading (with filtering) entries from disk and memory
|
|
||||||
func TestQueryLog(t *testing.T) {
|
func TestQueryLog(t *testing.T) {
|
||||||
conf := Config{
|
l := newQueryLog(Config{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileEnabled: true,
|
FileEnabled: true,
|
||||||
Interval: 1,
|
Interval: 1,
|
||||||
MemSize: 100,
|
MemSize: 100,
|
||||||
}
|
BaseDir: aghtest.PrepareTestDir(t),
|
||||||
conf.BaseDir = prepareTestDir()
|
})
|
||||||
defer func() { _ = os.RemoveAll(conf.BaseDir) }()
|
|
||||||
l := newQueryLog(conf)
|
|
||||||
|
|
||||||
// add disk entries
|
// Add disk entries.
|
||||||
addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
// write to disk (first file)
|
// Write to disk (first file).
|
||||||
_ = l.flushLogBuffer(true)
|
require.Nil(t, l.flushLogBuffer(true))
|
||||||
// start writing to the second file
|
// Start writing to the second file.
|
||||||
_ = l.rotate()
|
require.Nil(t, l.rotate())
|
||||||
// add disk entries
|
// Add disk entries.
|
||||||
addEntry(l, "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
addEntry(l, "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
||||||
// write to disk
|
// Write to disk.
|
||||||
_ = l.flushLogBuffer(true)
|
require.Nil(t, l.flushLogBuffer(true))
|
||||||
// add memory entries
|
// Add memory entries.
|
||||||
addEntry(l, "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
addEntry(l, "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
||||||
addEntry(l, "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4))
|
addEntry(l, "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4))
|
||||||
|
|
||||||
// get all entries
|
type tcAssertion struct {
|
||||||
params := newSearchParams()
|
num int
|
||||||
entries, _ := l.search(params)
|
host string
|
||||||
assert.Len(t, entries, 4)
|
answer, client net.IP
|
||||||
assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4))
|
}
|
||||||
assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
|
||||||
assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
|
||||||
assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
|
||||||
|
|
||||||
// search by domain (strict)
|
testCases := []struct {
|
||||||
params = newSearchParams()
|
name string
|
||||||
params.searchCriteria = append(params.searchCriteria, searchCriteria{
|
sCr []searchCriteria
|
||||||
criteriaType: ctDomainOrClient,
|
want []tcAssertion
|
||||||
strict: true,
|
}{{
|
||||||
value: "TEST.example.org",
|
name: "all",
|
||||||
})
|
sCr: []searchCriteria{},
|
||||||
entries, _ = l.search(params)
|
want: []tcAssertion{
|
||||||
assert.Len(t, entries, 1)
|
{num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
|
||||||
assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
{num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
|
||||||
|
{num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
|
||||||
|
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "by_domain_strict",
|
||||||
|
sCr: []searchCriteria{{
|
||||||
|
criteriaType: ctDomainOrClient,
|
||||||
|
strict: true,
|
||||||
|
value: "TEST.example.org",
|
||||||
|
}},
|
||||||
|
want: []tcAssertion{{
|
||||||
|
num: 0, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3),
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
name: "by_domain_non-strict",
|
||||||
|
sCr: []searchCriteria{{
|
||||||
|
criteriaType: ctDomainOrClient,
|
||||||
|
strict: false,
|
||||||
|
value: "example.ORG",
|
||||||
|
}},
|
||||||
|
want: []tcAssertion{
|
||||||
|
{num: 0, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
|
||||||
|
{num: 1, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
|
||||||
|
{num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "by_client_ip_strict",
|
||||||
|
sCr: []searchCriteria{{
|
||||||
|
criteriaType: ctDomainOrClient,
|
||||||
|
strict: true,
|
||||||
|
value: "2.2.2.2",
|
||||||
|
}},
|
||||||
|
want: []tcAssertion{{
|
||||||
|
num: 0, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2),
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
name: "by_client_ip_non-strict",
|
||||||
|
sCr: []searchCriteria{{
|
||||||
|
criteriaType: ctDomainOrClient,
|
||||||
|
strict: false,
|
||||||
|
value: "2.2.2",
|
||||||
|
}},
|
||||||
|
want: []tcAssertion{
|
||||||
|
{num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
|
||||||
|
{num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
|
||||||
|
{num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
|
||||||
|
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
// search by domain (not strict)
|
for _, tc := range testCases {
|
||||||
params = newSearchParams()
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
params.searchCriteria = append(params.searchCriteria, searchCriteria{
|
params := newSearchParams()
|
||||||
criteriaType: ctDomainOrClient,
|
params.searchCriteria = tc.sCr
|
||||||
strict: false,
|
|
||||||
value: "example.ORG",
|
|
||||||
})
|
|
||||||
entries, _ = l.search(params)
|
|
||||||
assert.Len(t, entries, 3)
|
|
||||||
assertLogEntry(t, entries[0], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
|
||||||
assertLogEntry(t, entries[1], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
|
||||||
assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
|
||||||
|
|
||||||
// search by client IP (strict)
|
entries, _ := l.search(params)
|
||||||
params = newSearchParams()
|
require.Len(t, entries, len(tc.want))
|
||||||
params.searchCriteria = append(params.searchCriteria, searchCriteria{
|
for _, want := range tc.want {
|
||||||
criteriaType: ctDomainOrClient,
|
assertLogEntry(t, entries[want.num], want.host, want.answer, want.client)
|
||||||
strict: true,
|
}
|
||||||
value: "2.2.2.2",
|
})
|
||||||
})
|
}
|
||||||
entries, _ = l.search(params)
|
|
||||||
assert.Len(t, entries, 1)
|
|
||||||
assertLogEntry(t, entries[0], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
|
||||||
|
|
||||||
// search by client IP (part of)
|
|
||||||
params = newSearchParams()
|
|
||||||
params.searchCriteria = append(params.searchCriteria, searchCriteria{
|
|
||||||
criteriaType: ctDomainOrClient,
|
|
||||||
strict: false,
|
|
||||||
value: "2.2.2",
|
|
||||||
})
|
|
||||||
entries, _ = l.search(params)
|
|
||||||
assert.Len(t, entries, 4)
|
|
||||||
assertLogEntry(t, entries[0], "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4))
|
|
||||||
assertLogEntry(t, entries[1], "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
|
|
||||||
assertLogEntry(t, entries[2], "example.org", net.IPv4(1, 1, 1, 2), net.IPv4(2, 2, 2, 2))
|
|
||||||
assertLogEntry(t, entries[3], "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryLogOffsetLimit(t *testing.T) {
|
func TestQueryLogOffsetLimit(t *testing.T) {
|
||||||
conf := Config{
|
l := newQueryLog(Config{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Interval: 1,
|
Interval: 1,
|
||||||
MemSize: 100,
|
MemSize: 100,
|
||||||
}
|
BaseDir: aghtest.PrepareTestDir(t),
|
||||||
conf.BaseDir = prepareTestDir()
|
})
|
||||||
defer func() { _ = os.RemoveAll(conf.BaseDir) }()
|
|
||||||
l := newQueryLog(conf)
|
|
||||||
|
|
||||||
// add 10 entries to the log
|
const (
|
||||||
for i := 0; i < 10; i++ {
|
entNum = 10
|
||||||
addEntry(l, "second.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
firstPageDomain = "first.example.org"
|
||||||
|
secondPageDomain = "second.example.org"
|
||||||
|
)
|
||||||
|
// Add entries to the log.
|
||||||
|
for i := 0; i < entNum; i++ {
|
||||||
|
addEntry(l, secondPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
}
|
}
|
||||||
// write them to disk (first file)
|
// Write them to the first file.
|
||||||
_ = l.flushLogBuffer(true)
|
require.Nil(t, l.flushLogBuffer(true))
|
||||||
// add 10 more entries to the log (memory)
|
// Add more to the in-memory part of log.
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < entNum; i++ {
|
||||||
addEntry(l, "first.example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, firstPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// First page
|
|
||||||
params := newSearchParams()
|
params := newSearchParams()
|
||||||
params.offset = 0
|
|
||||||
params.limit = 10
|
|
||||||
entries, _ := l.search(params)
|
|
||||||
assert.Len(t, entries, 10)
|
|
||||||
assert.Equal(t, entries[0].QHost, "first.example.org")
|
|
||||||
assert.Equal(t, entries[9].QHost, "first.example.org")
|
|
||||||
|
|
||||||
// Second page
|
testCases := []struct {
|
||||||
params.offset = 10
|
name string
|
||||||
params.limit = 10
|
offset int
|
||||||
entries, _ = l.search(params)
|
limit int
|
||||||
assert.Len(t, entries, 10)
|
wantLen int
|
||||||
assert.Equal(t, entries[0].QHost, "second.example.org")
|
want string
|
||||||
assert.Equal(t, entries[9].QHost, "second.example.org")
|
}{{
|
||||||
|
name: "page_1",
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
wantLen: 10,
|
||||||
|
want: firstPageDomain,
|
||||||
|
}, {
|
||||||
|
name: "page_2",
|
||||||
|
offset: 10,
|
||||||
|
limit: 10,
|
||||||
|
wantLen: 10,
|
||||||
|
want: secondPageDomain,
|
||||||
|
}, {
|
||||||
|
name: "page_2.5",
|
||||||
|
offset: 15,
|
||||||
|
limit: 10,
|
||||||
|
wantLen: 5,
|
||||||
|
want: secondPageDomain,
|
||||||
|
}, {
|
||||||
|
name: "page_3",
|
||||||
|
offset: 20,
|
||||||
|
limit: 10,
|
||||||
|
wantLen: 0,
|
||||||
|
}}
|
||||||
|
|
||||||
// Second and a half page
|
for _, tc := range testCases {
|
||||||
params.offset = 15
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
params.limit = 10
|
params.offset = tc.offset
|
||||||
entries, _ = l.search(params)
|
params.limit = tc.limit
|
||||||
assert.Len(t, entries, 5)
|
entries, _ := l.search(params)
|
||||||
assert.Equal(t, entries[0].QHost, "second.example.org")
|
|
||||||
assert.Equal(t, entries[4].QHost, "second.example.org")
|
|
||||||
|
|
||||||
// Third page
|
require.Len(t, entries, tc.wantLen)
|
||||||
params.offset = 20
|
|
||||||
params.limit = 10
|
if tc.wantLen > 0 {
|
||||||
entries, _ = l.search(params)
|
assert.Equal(t, entries[0].QHost, tc.want)
|
||||||
assert.Empty(t, entries)
|
assert.Equal(t, entries[tc.wantLen-1].QHost, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryLogMaxFileScanEntries(t *testing.T) {
|
func TestQueryLogMaxFileScanEntries(t *testing.T) {
|
||||||
conf := Config{
|
l := newQueryLog(Config{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileEnabled: true,
|
FileEnabled: true,
|
||||||
Interval: 1,
|
Interval: 1,
|
||||||
MemSize: 100,
|
MemSize: 100,
|
||||||
}
|
BaseDir: aghtest.PrepareTestDir(t),
|
||||||
conf.BaseDir = prepareTestDir()
|
})
|
||||||
defer func() { _ = os.RemoveAll(conf.BaseDir) }()
|
|
||||||
l := newQueryLog(conf)
|
|
||||||
|
|
||||||
// add 10 entries to the log
|
const entNum = 10
|
||||||
for i := 0; i < 10; i++ {
|
// Add entries to the log.
|
||||||
|
for i := 0; i < entNum; i++ {
|
||||||
addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
}
|
}
|
||||||
// write them to disk (first file)
|
// Write them to disk.
|
||||||
_ = l.flushLogBuffer(true)
|
require.Nil(t, l.flushLogBuffer(true))
|
||||||
|
|
||||||
params := newSearchParams()
|
params := newSearchParams()
|
||||||
params.maxFileScanEntries = 5 // do not scan more than 5 records
|
|
||||||
entries, _ := l.search(params)
|
|
||||||
assert.Len(t, entries, 5)
|
|
||||||
|
|
||||||
params.maxFileScanEntries = 0 // disable the limit
|
for _, maxFileScanEntries := range []int{5, 0} {
|
||||||
entries, _ = l.search(params)
|
t.Run(fmt.Sprintf("limit_%d", maxFileScanEntries), func(t *testing.T) {
|
||||||
assert.Len(t, entries, 10)
|
params.maxFileScanEntries = maxFileScanEntries
|
||||||
|
entries, _ := l.search(params)
|
||||||
|
assert.Len(t, entries, entNum-maxFileScanEntries)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryLogFileDisabled(t *testing.T) {
|
func TestQueryLogFileDisabled(t *testing.T) {
|
||||||
conf := Config{
|
l := newQueryLog(Config{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileEnabled: false,
|
FileEnabled: false,
|
||||||
Interval: 1,
|
Interval: 1,
|
||||||
MemSize: 2,
|
MemSize: 2,
|
||||||
}
|
BaseDir: aghtest.PrepareTestDir(t),
|
||||||
conf.BaseDir = prepareTestDir()
|
})
|
||||||
defer func() { _ = os.RemoveAll(conf.BaseDir) }()
|
|
||||||
l := newQueryLog(conf)
|
|
||||||
|
|
||||||
addEntry(l, "example1.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, "example1.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
addEntry(l, "example2.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, "example2.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
|
// The oldest entry is going to be removed from memory buffer.
|
||||||
addEntry(l, "example3.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
addEntry(l, "example3.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||||
// the oldest entry is now removed from mem buffer
|
|
||||||
|
|
||||||
params := newSearchParams()
|
params := newSearchParams()
|
||||||
ll, _ := l.search(params)
|
ll, _ := l.search(params)
|
||||||
assert.Len(t, ll, 2)
|
require.Len(t, ll, 2)
|
||||||
assert.Equal(t, "example3.org", ll[0].QHost)
|
assert.Equal(t, "example3.org", ll[0].QHost)
|
||||||
assert.Equal(t, "example2.org", ll[1].QHost)
|
assert.Equal(t, "example2.org", ll[1].QHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addEntry(l *queryLog, host string, answerStr, client net.IP) {
|
func addEntry(l *queryLog, host string, answerStr, client net.IP) {
|
||||||
q := dns.Msg{}
|
q := dns.Msg{
|
||||||
q.Question = append(q.Question, dns.Question{
|
Question: []dns.Question{{
|
||||||
Name: host + ".",
|
Name: host + ".",
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
})
|
}},
|
||||||
|
}
|
||||||
a := dns.Msg{}
|
|
||||||
a.Question = append(a.Question, q.Question[0])
|
a := dns.Msg{
|
||||||
answer := new(dns.A)
|
Question: q.Question,
|
||||||
answer.Hdr = dns.RR_Header{
|
Answer: []dns.RR{&dns.A{
|
||||||
Name: q.Question[0].Name,
|
Hdr: dns.RR_Header{
|
||||||
Rrtype: dns.TypeA,
|
Name: q.Question[0].Name,
|
||||||
Class: dns.ClassINET,
|
Rrtype: dns.TypeA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
},
|
||||||
|
A: answerStr,
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
answer.A = answerStr
|
|
||||||
a.Answer = append(a.Answer, answer)
|
|
||||||
res := dnsfilter.Result{
|
res := dnsfilter.Result{
|
||||||
IsFiltered: true,
|
IsFiltered: true,
|
||||||
Reason: dnsfilter.Rewritten,
|
Reason: dnsfilter.Rewritten,
|
||||||
@@ -254,19 +288,22 @@ func addEntry(l *queryLog, host string, answerStr, client net.IP) {
|
|||||||
l.Add(params)
|
l.Add(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client net.IP) bool {
|
func assertLogEntry(t *testing.T, entry *logEntry, host string, answer, client net.IP) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
|
||||||
assert.Equal(t, host, entry.QHost)
|
assert.Equal(t, host, entry.QHost)
|
||||||
assert.Equal(t, client, entry.IP)
|
assert.Equal(t, client, entry.IP)
|
||||||
assert.Equal(t, "A", entry.QType)
|
assert.Equal(t, "A", entry.QType)
|
||||||
assert.Equal(t, "IN", entry.QClass)
|
assert.Equal(t, "IN", entry.QClass)
|
||||||
|
|
||||||
msg := new(dns.Msg)
|
msg := &dns.Msg{}
|
||||||
assert.Nil(t, msg.Unpack(entry.Answer))
|
require.Nil(t, msg.Unpack(entry.Answer))
|
||||||
assert.Len(t, msg.Answer, 1)
|
require.Len(t, msg.Answer, 1)
|
||||||
|
|
||||||
ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]).To16()
|
ip := proxyutil.GetIPFromDNSRecord(msg.Answer[0]).To16()
|
||||||
assert.NotNil(t, ip)
|
|
||||||
assert.Equal(t, answer, ip)
|
assert.Equal(t, answer, ip)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntries() (entries []*logEntry) {
|
func testEntries() (entries []*logEntry) {
|
||||||
@@ -332,8 +369,8 @@ func TestLogEntriesByTime_sort(t *testing.T) {
|
|||||||
entries := testEntries()
|
entries := testEntries()
|
||||||
sort.Sort(logEntriesByTimeDesc(entries))
|
sort.Sort(logEntriesByTimeDesc(entries))
|
||||||
|
|
||||||
for i := 1; i < len(entries); i++ {
|
for i := range entries[1:] {
|
||||||
assert.False(t, entries[i].Time.After(entries[i-1].Time),
|
assert.False(t, entries[i+1].Time.After(entries[i].Time),
|
||||||
"%s %s", entries[i].Time, entries[i-1].Time)
|
"%s %s", entries[i+1].Time, entries[i].Time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,347 +2,347 @@ package querylog
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQLogFileEmpty(t *testing.T) {
|
// prepareTestFiles prepares several test query log files, each with the
|
||||||
testDir := prepareTestDir()
|
// specified lines count.
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
func prepareTestFiles(t *testing.T, filesNum, linesNum int) []string {
|
||||||
testFile := prepareTestFile(testDir, 0)
|
t.Helper()
|
||||||
|
|
||||||
// create the new QLogFile instance
|
if filesNum == 0 {
|
||||||
q, err := NewQLogFile(testFile)
|
return []string{}
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, q)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
|
||||||
pos, err := q.SeekStart()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.EqualValues(t, 0, pos)
|
|
||||||
|
|
||||||
// try reading anyway
|
|
||||||
line, err := q.ReadNext()
|
|
||||||
assert.Equal(t, io.EOF, err)
|
|
||||||
assert.Empty(t, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQLogFileLarge(t *testing.T) {
|
|
||||||
// should be large enough
|
|
||||||
count := 50000
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFile := prepareTestFile(testDir, count)
|
|
||||||
|
|
||||||
// create the new QLogFile instance
|
|
||||||
q, err := NewQLogFile(testFile)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, q)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
|
||||||
pos, err := q.SeekStart()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotEqualValues(t, 0, pos)
|
|
||||||
|
|
||||||
read := 0
|
|
||||||
var line string
|
|
||||||
for err == nil {
|
|
||||||
line, err = q.ReadNext()
|
|
||||||
if err == nil {
|
|
||||||
assert.NotZero(t, len(line))
|
|
||||||
read++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, count, read)
|
const strV = "\"%s\""
|
||||||
assert.Equal(t, io.EOF, err)
|
const nl = "\n"
|
||||||
}
|
const format = `{"IP":` + strV + `,"T":` + strV + `,` +
|
||||||
|
`"QH":"example.org","QT":"A","QC":"IN",` +
|
||||||
func TestQLogFileSeekLargeFile(t *testing.T) {
|
`"Answer":"AAAAAAABAAEAAAAAB2V4YW1wbGUDb3JnAAABAAEHZXhhbXBsZQNvcmcAAAEAAQAAAAAABAECAwQ=",` +
|
||||||
// more or less big file
|
`"Result":{},"Elapsed":0,"Upstream":"upstream"}` + nl
|
||||||
count := 10000
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFile := prepareTestFile(testDir, count)
|
|
||||||
|
|
||||||
// create the new QLogFile instance
|
|
||||||
q, err := NewQLogFile(testFile)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, q)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
// CASE 1: NOT TOO OLD LINE
|
|
||||||
testSeekLineQLogFile(t, q, 300)
|
|
||||||
|
|
||||||
// CASE 2: OLD LINE
|
|
||||||
testSeekLineQLogFile(t, q, count-300)
|
|
||||||
|
|
||||||
// CASE 3: FIRST LINE
|
|
||||||
testSeekLineQLogFile(t, q, 0)
|
|
||||||
|
|
||||||
// CASE 4: LAST LINE
|
|
||||||
testSeekLineQLogFile(t, q, count)
|
|
||||||
|
|
||||||
// CASE 5: Seek non-existent (too low)
|
|
||||||
_, _, err = q.SeekTS(123)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// CASE 6: Seek non-existent (too high)
|
|
||||||
ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
|
||||||
_, _, err = q.SeekTS(ts.UnixNano())
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// CASE 7: "Almost" found
|
|
||||||
line, err := getQLogFileLine(q, count/2)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
// ALMOST the record we need
|
|
||||||
timestamp := readQLogTimestamp(line) - 1
|
|
||||||
assert.NotEqualValues(t, 0, timestamp)
|
|
||||||
_, depth, err := q.SeekTS(timestamp)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQLogFileSeekSmallFile(t *testing.T) {
|
|
||||||
// more or less big file
|
|
||||||
count := 10
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFile := prepareTestFile(testDir, count)
|
|
||||||
|
|
||||||
// create the new QLogFile instance
|
|
||||||
q, err := NewQLogFile(testFile)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, q)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
// CASE 1: NOT TOO OLD LINE
|
|
||||||
testSeekLineQLogFile(t, q, 2)
|
|
||||||
|
|
||||||
// CASE 2: OLD LINE
|
|
||||||
testSeekLineQLogFile(t, q, count-2)
|
|
||||||
|
|
||||||
// CASE 3: FIRST LINE
|
|
||||||
testSeekLineQLogFile(t, q, 0)
|
|
||||||
|
|
||||||
// CASE 4: LAST LINE
|
|
||||||
testSeekLineQLogFile(t, q, count)
|
|
||||||
|
|
||||||
// CASE 5: Seek non-existent (too low)
|
|
||||||
_, _, err = q.SeekTS(123)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// CASE 6: Seek non-existent (too high)
|
|
||||||
ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
|
||||||
_, _, err = q.SeekTS(ts.UnixNano())
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// CASE 7: "Almost" found
|
|
||||||
line, err := getQLogFileLine(q, count/2)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
// ALMOST the record we need
|
|
||||||
timestamp := readQLogTimestamp(line) - 1
|
|
||||||
assert.NotEqualValues(t, 0, timestamp)
|
|
||||||
_, depth, err := q.SeekTS(timestamp)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.LessOrEqual(t, depth, int(math.Log2(float64(count))+3))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSeekLineQLogFile(t *testing.T, q *QLogFile, lineNumber int) {
|
|
||||||
line, err := getQLogFileLine(q, lineNumber)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
ts := readQLogTimestamp(line)
|
|
||||||
assert.NotEqualValues(t, 0, ts)
|
|
||||||
|
|
||||||
// try seeking to that line now
|
|
||||||
pos, _, err := q.SeekTS(ts)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotEqualValues(t, 0, pos)
|
|
||||||
|
|
||||||
testLine, err := q.ReadNext()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, line, testLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQLogFileLine(q *QLogFile, lineNumber int) (string, error) {
|
|
||||||
_, err := q.SeekStart()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < lineNumber; i++ {
|
|
||||||
_, err := q.ReadNext()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q.ReadNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check adding and loading (with filtering) entries from disk and memory
|
|
||||||
func TestQLogFile(t *testing.T) {
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFile := prepareTestFile(testDir, 2)
|
|
||||||
|
|
||||||
// create the new QLogFile instance
|
|
||||||
q, err := NewQLogFile(testFile)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, q)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
|
||||||
pos, err := q.SeekStart()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Greater(t, pos, int64(0))
|
|
||||||
|
|
||||||
// read first line
|
|
||||||
line, err := q.ReadNext()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Contains(t, line, "0.0.0.2")
|
|
||||||
assert.True(t, strings.HasPrefix(line, "{"), line)
|
|
||||||
assert.True(t, strings.HasSuffix(line, "}"), line)
|
|
||||||
|
|
||||||
// read second line
|
|
||||||
line, err = q.ReadNext()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.EqualValues(t, 0, q.position)
|
|
||||||
assert.Contains(t, line, "0.0.0.1")
|
|
||||||
assert.True(t, strings.HasPrefix(line, "{"), line)
|
|
||||||
assert.True(t, strings.HasSuffix(line, "}"), line)
|
|
||||||
|
|
||||||
// try reading again (there's nothing to read anymore)
|
|
||||||
line, err = q.ReadNext()
|
|
||||||
assert.Equal(t, io.EOF, err)
|
|
||||||
assert.Empty(t, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareTestFile - prepares a test query log file with the specified number of lines
|
|
||||||
func prepareTestFile(dir string, linesCount int) string {
|
|
||||||
return prepareTestFiles(dir, 1, linesCount)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareTestFiles - prepares several test query log files
|
|
||||||
// each of them -- with the specified linesCount
|
|
||||||
func prepareTestFiles(dir string, filesCount, linesCount int) []string {
|
|
||||||
format := `{"IP":"${IP}","T":"${TIMESTAMP}","QH":"example.org","QT":"A","QC":"IN","Answer":"AAAAAAABAAEAAAAAB2V4YW1wbGUDb3JnAAABAAEHZXhhbXBsZQNvcmcAAAEAAQAAAAAABAECAwQ=","Result":{},"Elapsed":0,"Upstream":"upstream"}`
|
|
||||||
|
|
||||||
lineTime, _ := time.Parse(time.RFC3339Nano, "2020-02-18T22:36:35.920973+03:00")
|
lineTime, _ := time.Parse(time.RFC3339Nano, "2020-02-18T22:36:35.920973+03:00")
|
||||||
lineIP := uint32(0)
|
lineIP := uint32(0)
|
||||||
|
|
||||||
files := make([]string, filesCount)
|
dir := aghtest.PrepareTestDir(t)
|
||||||
for j := 0; j < filesCount; j++ {
|
|
||||||
f, _ := ioutil.TempFile(dir, "*.txt")
|
|
||||||
files[filesCount-j-1] = f.Name()
|
|
||||||
|
|
||||||
for i := 0; i < linesCount; i++ {
|
files := make([]string, filesNum)
|
||||||
|
for j := range files {
|
||||||
|
f, err := ioutil.TempFile(dir, "*.txt")
|
||||||
|
require.Nil(t, err)
|
||||||
|
files[filesNum-j-1] = f.Name()
|
||||||
|
|
||||||
|
for i := 0; i < linesNum; i++ {
|
||||||
lineIP++
|
lineIP++
|
||||||
lineTime = lineTime.Add(time.Second)
|
lineTime = lineTime.Add(time.Second)
|
||||||
|
|
||||||
ip := make(net.IP, 4)
|
ip := make(net.IP, 4)
|
||||||
binary.BigEndian.PutUint32(ip, lineIP)
|
binary.BigEndian.PutUint32(ip, lineIP)
|
||||||
|
|
||||||
line := format
|
line := fmt.Sprintf(format, ip, lineTime.Format(time.RFC3339Nano))
|
||||||
line = strings.ReplaceAll(line, "${IP}", ip.String())
|
|
||||||
line = strings.ReplaceAll(line, "${TIMESTAMP}", lineTime.Format(time.RFC3339Nano))
|
|
||||||
|
|
||||||
_, _ = f.WriteString(line)
|
_, err = f.WriteString(line)
|
||||||
_, _ = f.WriteString("\n")
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogSeek(t *testing.T) {
|
// prepareTestFile prepares a test query log file with the specified number of
|
||||||
testDir := prepareTestDir()
|
// lines.
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
func prepareTestFile(t *testing.T, linesCount int) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}
|
return prepareTestFiles(t, 1, linesCount)[0]
|
||||||
{"T":"2020-08-31T18:44:25.376690873+03:00"}
|
|
||||||
{"T":"2020-08-31T18:44:25.382540454+03:00"}`
|
|
||||||
f, _ := ioutil.TempFile(testDir, "*.txt")
|
|
||||||
_, _ = f.WriteString(d)
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
q, err := NewQLogFile(f.Name())
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
target, _ := time.Parse(time.RFC3339, "2020-08-31T18:44:25.376690873+03:00")
|
|
||||||
|
|
||||||
_, depth, err := q.SeekTS(target.UnixNano())
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, 1, depth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogSeek_ErrTSTooLate(t *testing.T) {
|
// newTestQLogFile creates new *QLogFile for tests and registers the required
|
||||||
testDir := prepareTestDir()
|
// cleanup functions.
|
||||||
|
func newTestQLogFile(t *testing.T, linesNum int) (file *QLogFile) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
testFile := prepareTestFile(t, linesNum)
|
||||||
|
|
||||||
|
// Create the new QLogFile instance.
|
||||||
|
file, err := NewQLogFile(testFile)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.NotNil(t, file)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = os.RemoveAll(testDir)
|
assert.Nil(t, file.Close())
|
||||||
})
|
})
|
||||||
|
|
||||||
d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}
|
return file
|
||||||
{"T":"2020-08-31T18:44:25.376690873+03:00"}
|
|
||||||
{"T":"2020-08-31T18:44:25.382540454+03:00"}
|
|
||||||
`
|
|
||||||
f, err := ioutil.TempFile(testDir, "*.txt")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = f.WriteString(d)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
q, err := NewQLogFile(f.Name())
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer q.Close()
|
|
||||||
|
|
||||||
target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:25.382540454+03:00")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
_, depth, err := q.SeekTS(target.UnixNano() + int64(time.Second))
|
|
||||||
assert.Equal(t, ErrTSTooLate, err)
|
|
||||||
assert.Equal(t, 2, depth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogSeek_ErrTSTooEarly(t *testing.T) {
|
func TestQLogFile_ReadNext(t *testing.T) {
|
||||||
testDir := prepareTestDir()
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
linesNum int
|
||||||
|
}{{
|
||||||
|
name: "empty",
|
||||||
|
linesNum: 0,
|
||||||
|
}, {
|
||||||
|
name: "large",
|
||||||
|
linesNum: 50000,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
q := newTestQLogFile(t, tc.linesNum)
|
||||||
|
|
||||||
|
// Calculate the expected position.
|
||||||
|
fileInfo, err := q.file.Stat()
|
||||||
|
require.Nil(t, err)
|
||||||
|
var expPos int64
|
||||||
|
if expPos = fileInfo.Size(); expPos > 0 {
|
||||||
|
expPos--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to the start.
|
||||||
|
pos, err := q.SeekStart()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.EqualValues(t, expPos, pos)
|
||||||
|
|
||||||
|
var read int
|
||||||
|
var line string
|
||||||
|
for err == nil {
|
||||||
|
line, err = q.ReadNext()
|
||||||
|
if err == nil {
|
||||||
|
assert.NotEmpty(t, line)
|
||||||
|
read++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, io.EOF, err)
|
||||||
|
assert.Equal(t, tc.linesNum, read)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQLogFile_SeekTS_good(t *testing.T) {
|
||||||
|
linesCases := []struct {
|
||||||
|
name string
|
||||||
|
num int
|
||||||
|
}{{
|
||||||
|
name: "large",
|
||||||
|
num: 10000,
|
||||||
|
}, {
|
||||||
|
name: "small",
|
||||||
|
num: 10,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, l := range linesCases {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
linesNum int
|
||||||
|
line int
|
||||||
|
}{{
|
||||||
|
name: "not_too_old",
|
||||||
|
line: 2,
|
||||||
|
}, {
|
||||||
|
name: "old",
|
||||||
|
line: l.num - 2,
|
||||||
|
}, {
|
||||||
|
name: "first",
|
||||||
|
line: 0,
|
||||||
|
}, {
|
||||||
|
name: "last",
|
||||||
|
line: l.num,
|
||||||
|
}}
|
||||||
|
|
||||||
|
q := newTestQLogFile(t, l.num)
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(l.name+"_"+tc.name, func(t *testing.T) {
|
||||||
|
line, err := getQLogFileLine(q, tc.line)
|
||||||
|
require.Nil(t, err)
|
||||||
|
ts := readQLogTimestamp(line)
|
||||||
|
assert.NotEqualValues(t, 0, ts)
|
||||||
|
|
||||||
|
// Try seeking to that line now.
|
||||||
|
pos, _, err := q.SeekTS(ts)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.NotEqualValues(t, 0, pos)
|
||||||
|
|
||||||
|
testLine, err := q.ReadNext()
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, line, testLine)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQLogFile_SeekTS_bad(t *testing.T) {
|
||||||
|
linesCases := []struct {
|
||||||
|
name string
|
||||||
|
num int
|
||||||
|
}{{
|
||||||
|
name: "large",
|
||||||
|
num: 10000,
|
||||||
|
}, {
|
||||||
|
name: "small",
|
||||||
|
num: 10,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, l := range linesCases {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
ts int64
|
||||||
|
leq bool
|
||||||
|
}{{
|
||||||
|
name: "non-existent_long_ago",
|
||||||
|
}, {
|
||||||
|
name: "non-existent_far_ahead",
|
||||||
|
}, {
|
||||||
|
name: "almost",
|
||||||
|
leq: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
q := newTestQLogFile(t, l.num)
|
||||||
|
testCases[0].ts = 123
|
||||||
|
|
||||||
|
lateTS, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
||||||
|
testCases[1].ts = lateTS.UnixNano()
|
||||||
|
|
||||||
|
line, err := getQLogFileLine(q, l.num/2)
|
||||||
|
require.Nil(t, err)
|
||||||
|
testCases[2].ts = readQLogTimestamp(line) - 1
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.NotEqualValues(t, 0, tc.ts)
|
||||||
|
|
||||||
|
_, depth, err := q.SeekTS(tc.ts)
|
||||||
|
assert.NotEmpty(t, l.num)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
if tc.leq {
|
||||||
|
assert.LessOrEqual(t, depth, int(math.Log2(float64(l.num))+3))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQLogFileLine(q *QLogFile, lineNumber int) (line string, err error) {
|
||||||
|
if _, err = q.SeekStart(); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < lineNumber; i++ {
|
||||||
|
if _, err = q.ReadNext(); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.ReadNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check adding and loading (with filtering) entries from disk and memory.
|
||||||
|
func TestQLogFile(t *testing.T) {
|
||||||
|
// Create the new QLogFile instance.
|
||||||
|
q := newTestQLogFile(t, 2)
|
||||||
|
|
||||||
|
// Seek to the start.
|
||||||
|
pos, err := q.SeekStart()
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Greater(t, pos, int64(0))
|
||||||
|
|
||||||
|
// Read first line.
|
||||||
|
line, err := q.ReadNext()
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Contains(t, line, "0.0.0.2")
|
||||||
|
assert.True(t, strings.HasPrefix(line, "{"), line)
|
||||||
|
assert.True(t, strings.HasSuffix(line, "}"), line)
|
||||||
|
|
||||||
|
// Read second line.
|
||||||
|
line, err = q.ReadNext()
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.EqualValues(t, 0, q.position)
|
||||||
|
assert.Contains(t, line, "0.0.0.1")
|
||||||
|
assert.True(t, strings.HasPrefix(line, "{"), line)
|
||||||
|
assert.True(t, strings.HasSuffix(line, "}"), line)
|
||||||
|
|
||||||
|
// Try reading again (there's nothing to read anymore).
|
||||||
|
line, err = q.ReadNext()
|
||||||
|
require.Equal(t, io.EOF, err)
|
||||||
|
assert.Empty(t, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) {
|
||||||
|
f, err := ioutil.TempFile(aghtest.PrepareTestDir(t), "*.txt")
|
||||||
|
require.Nil(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = os.RemoveAll(testDir)
|
assert.Nil(t, f.Close())
|
||||||
})
|
})
|
||||||
|
|
||||||
d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}
|
_, err = f.WriteString(data)
|
||||||
{"T":"2020-08-31T18:44:25.376690873+03:00"}
|
require.Nil(t, err)
|
||||||
{"T":"2020-08-31T18:44:25.382540454+03:00"}
|
|
||||||
`
|
|
||||||
f, err := ioutil.TempFile(testDir, "*.txt")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = f.WriteString(d)
|
file, err = NewQLogFile(f.Name())
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.Nil(t, file.Close())
|
||||||
|
})
|
||||||
|
|
||||||
q, err := NewQLogFile(f.Name())
|
return file
|
||||||
assert.Nil(t, err)
|
}
|
||||||
defer q.Close()
|
|
||||||
|
func TestQLog_Seek(t *testing.T) {
|
||||||
target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:23.911246629+03:00")
|
const nl = "\n"
|
||||||
assert.Nil(t, err)
|
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 +
|
||||||
_, depth, err := q.SeekTS(target.UnixNano() - int64(time.Second))
|
`{"T":"` + strV + `"}` + nl +
|
||||||
assert.Equal(t, ErrTSTooEarly, err)
|
`{"T":"` + strV + `"}` + nl
|
||||||
assert.Equal(t, 1, depth)
|
timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
delta int
|
||||||
|
wantErr error
|
||||||
|
wantDepth int
|
||||||
|
}{{
|
||||||
|
name: "ok",
|
||||||
|
delta: 0,
|
||||||
|
wantErr: nil,
|
||||||
|
wantDepth: 2,
|
||||||
|
}, {
|
||||||
|
name: "too_late",
|
||||||
|
delta: 2,
|
||||||
|
wantErr: ErrTSTooLate,
|
||||||
|
wantDepth: 2,
|
||||||
|
}, {
|
||||||
|
name: "too_early",
|
||||||
|
delta: -2,
|
||||||
|
wantErr: ErrTSTooEarly,
|
||||||
|
wantDepth: 1,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
data := fmt.Sprintf(recs,
|
||||||
|
timestamp.Add(-time.Second).Format(time.RFC3339Nano),
|
||||||
|
timestamp.Format(time.RFC3339Nano),
|
||||||
|
timestamp.Add(time.Second).Format(time.RFC3339Nano),
|
||||||
|
)
|
||||||
|
|
||||||
|
q := NewTestQLogFileData(t, data)
|
||||||
|
|
||||||
|
_, depth, err := q.SeekTS(timestamp.Add(time.Second * time.Duration(tc.delta)).UnixNano())
|
||||||
|
require.Truef(t, errors.Is(err, tc.wantErr), "%v", err)
|
||||||
|
assert.Equal(t, tc.wantDepth, depth)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,110 +3,77 @@ package querylog
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQLogReaderEmpty(t *testing.T) {
|
// newTestQLogReader creates new *QLogReader for tests and registers the
|
||||||
r, err := NewQLogReader([]string{})
|
// required cleanup functions.
|
||||||
assert.Nil(t, err)
|
func newTestQLogReader(t *testing.T, filesNum, linesNum int) (reader *QLogReader) {
|
||||||
assert.NotNil(t, r)
|
t.Helper()
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
testFiles := prepareTestFiles(t, filesNum, linesNum)
|
||||||
err = r.SeekStart()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
line, err := r.ReadNext()
|
// Create the new QLogReader instance.
|
||||||
assert.Empty(t, line)
|
reader, err := NewQLogReader(testFiles)
|
||||||
assert.Equal(t, io.EOF, err)
|
require.Nil(t, err)
|
||||||
|
assert.NotNil(t, reader)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.Nil(t, reader.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
return reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogReaderOneFile(t *testing.T) {
|
func TestQLogReader(t *testing.T) {
|
||||||
// let's do one small file
|
testCases := []struct {
|
||||||
count := 10
|
name string
|
||||||
filesCount := 1
|
filesNum int
|
||||||
|
linesNum int
|
||||||
|
}{{
|
||||||
|
name: "empty",
|
||||||
|
filesNum: 0,
|
||||||
|
linesNum: 0,
|
||||||
|
}, {
|
||||||
|
name: "one_file",
|
||||||
|
filesNum: 1,
|
||||||
|
linesNum: 10,
|
||||||
|
}, {
|
||||||
|
name: "multiple_files",
|
||||||
|
filesNum: 5,
|
||||||
|
linesNum: 10000,
|
||||||
|
}}
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
for _, tc := range testCases {
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testFiles := prepareTestFiles(testDir, filesCount, count)
|
r := newTestQLogReader(t, tc.filesNum, tc.linesNum)
|
||||||
|
|
||||||
r, err := NewQLogReader(testFiles)
|
// Seek to the start.
|
||||||
assert.Nil(t, err)
|
err := r.SeekStart()
|
||||||
assert.NotNil(t, r)
|
require.Nil(t, err)
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
// Read everything.
|
||||||
err = r.SeekStart()
|
var read int
|
||||||
assert.Nil(t, err)
|
var line string
|
||||||
|
for err == nil {
|
||||||
|
line, err = r.ReadNext()
|
||||||
|
if err == nil {
|
||||||
|
assert.NotEmpty(t, line)
|
||||||
|
read++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// read everything
|
require.Equal(t, io.EOF, err)
|
||||||
read := 0
|
assert.Equal(t, tc.filesNum*tc.linesNum, read)
|
||||||
var line string
|
})
|
||||||
for err == nil {
|
|
||||||
line, err = r.ReadNext()
|
|
||||||
if err == nil {
|
|
||||||
assert.True(t, len(line) > 0)
|
|
||||||
read++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, count*filesCount, read)
|
|
||||||
assert.Equal(t, io.EOF, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQLogReaderMultipleFiles(t *testing.T) {
|
|
||||||
// should be large enough
|
|
||||||
count := 10000
|
|
||||||
filesCount := 5
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFiles := prepareTestFiles(testDir, filesCount, count)
|
|
||||||
|
|
||||||
r, err := NewQLogReader(testFiles)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, r)
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
// seek to the start
|
|
||||||
err = r.SeekStart()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// read everything
|
|
||||||
read := 0
|
|
||||||
var line string
|
|
||||||
for err == nil {
|
|
||||||
line, err = r.ReadNext()
|
|
||||||
if err == nil {
|
|
||||||
assert.True(t, len(line) > 0)
|
|
||||||
read++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, count*filesCount, read)
|
|
||||||
assert.Equal(t, io.EOF, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogReader_Seek(t *testing.T) {
|
func TestQLogReader_Seek(t *testing.T) {
|
||||||
count := 10000
|
r := newTestQLogReader(t, 2, 10000)
|
||||||
filesCount := 2
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = os.RemoveAll(testDir)
|
|
||||||
})
|
|
||||||
testFiles := prepareTestFiles(testDir, filesCount, count)
|
|
||||||
|
|
||||||
r, err := NewQLogReader(testFiles)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, r)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = r.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -114,7 +81,7 @@ func TestQLogReader_Seek(t *testing.T) {
|
|||||||
want error
|
want error
|
||||||
}{{
|
}{{
|
||||||
name: "not_too_old",
|
name: "not_too_old",
|
||||||
time: "2020-02-19T04:04:56.920973+03:00",
|
time: "2020-02-18T22:39:35.920973+03:00",
|
||||||
want: nil,
|
want: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "old",
|
name: "old",
|
||||||
@@ -122,7 +89,7 @@ func TestQLogReader_Seek(t *testing.T) {
|
|||||||
want: nil,
|
want: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "first",
|
name: "first",
|
||||||
time: "2020-02-19T04:09:55.920973+03:00",
|
time: "2020-02-18T22:36:36.920973+03:00",
|
||||||
want: nil,
|
want: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "last",
|
name: "last",
|
||||||
@@ -147,28 +114,20 @@ func TestQLogReader_Seek(t *testing.T) {
|
|||||||
timestamp, err := time.Parse(time.RFC3339Nano, tc.time)
|
timestamp, err := time.Parse(time.RFC3339Nano, tc.time)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if tc.name == "first" {
|
||||||
|
assert.True(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
err = r.SeekTS(timestamp.UnixNano())
|
err = r.SeekTS(timestamp.UnixNano())
|
||||||
assert.True(t, errors.Is(err, tc.want), err)
|
assert.True(t, errors.Is(err, tc.want))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQLogReader_ReadNext(t *testing.T) {
|
func TestQLogReader_ReadNext(t *testing.T) {
|
||||||
count := 10
|
const linesNum = 10
|
||||||
filesCount := 1
|
const filesNum = 1
|
||||||
|
r := newTestQLogReader(t, filesNum, linesNum)
|
||||||
testDir := prepareTestDir()
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = os.RemoveAll(testDir)
|
|
||||||
})
|
|
||||||
testFiles := prepareTestFiles(testDir, filesCount, count)
|
|
||||||
|
|
||||||
r, err := NewQLogReader(testFiles)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, r)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = r.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -180,7 +139,7 @@ func TestQLogReader_ReadNext(t *testing.T) {
|
|||||||
want: nil,
|
want: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "too_big",
|
name: "too_big",
|
||||||
start: count + 1,
|
start: linesNum + 1,
|
||||||
want: io.EOF,
|
want: io.EOF,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -199,70 +158,3 @@ func TestQLogReader_ReadNext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): Remove the tests below. Make tests above more compelling.
|
|
||||||
func TestQLogReaderSeek(t *testing.T) {
|
|
||||||
// more or less big file
|
|
||||||
count := 10000
|
|
||||||
filesCount := 2
|
|
||||||
|
|
||||||
testDir := prepareTestDir()
|
|
||||||
defer func() { _ = os.RemoveAll(testDir) }()
|
|
||||||
testFiles := prepareTestFiles(testDir, filesCount, count)
|
|
||||||
|
|
||||||
r, err := NewQLogReader(testFiles)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, r)
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
// CASE 1: NOT TOO OLD LINE
|
|
||||||
testSeekLineQLogReader(t, r, 300)
|
|
||||||
|
|
||||||
// CASE 2: OLD LINE
|
|
||||||
testSeekLineQLogReader(t, r, count-300)
|
|
||||||
|
|
||||||
// CASE 3: FIRST LINE
|
|
||||||
testSeekLineQLogReader(t, r, 0)
|
|
||||||
|
|
||||||
// CASE 4: LAST LINE
|
|
||||||
testSeekLineQLogReader(t, r, count)
|
|
||||||
|
|
||||||
// CASE 5: Seek non-existent (too low)
|
|
||||||
err = r.SeekTS(123)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
// CASE 6: Seek non-existent (too high)
|
|
||||||
ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
|
||||||
err = r.SeekTS(ts.UnixNano())
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSeekLineQLogReader(t *testing.T, r *QLogReader, lineNumber int) {
|
|
||||||
line, err := getQLogReaderLine(r, lineNumber)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
ts := readQLogTimestamp(line)
|
|
||||||
assert.NotEqualValues(t, 0, ts)
|
|
||||||
|
|
||||||
// try seeking to that line now
|
|
||||||
err = r.SeekTS(ts)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
testLine, err := r.ReadNext()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, line, testLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQLogReaderLine(r *QLogReader, lineNumber int) (string, error) {
|
|
||||||
err := r.SeekStart()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < lineNumber; i++ {
|
|
||||||
_, err := r.ReadNext()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.ReadNext()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@@ -34,28 +35,30 @@ func TestStats(t *testing.T) {
|
|||||||
Filename: "./stats.db",
|
Filename: "./stats.db",
|
||||||
LimitDays: 1,
|
LimitDays: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s, err := createObject(conf)
|
||||||
|
require.Nil(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
s.clear()
|
||||||
|
s.Close()
|
||||||
assert.Nil(t, os.Remove(conf.Filename))
|
assert.Nil(t, os.Remove(conf.Filename))
|
||||||
})
|
})
|
||||||
|
|
||||||
s, _ := createObject(conf)
|
s.Update(Entry{
|
||||||
|
Domain: "domain",
|
||||||
e := Entry{}
|
Client: "127.0.0.1",
|
||||||
|
Result: RFiltered,
|
||||||
e.Domain = "domain"
|
Time: 123456,
|
||||||
e.Client = "127.0.0.1"
|
})
|
||||||
e.Result = RFiltered
|
s.Update(Entry{
|
||||||
e.Time = 123456
|
Domain: "domain",
|
||||||
s.Update(e)
|
Client: "127.0.0.1",
|
||||||
|
Result: RNotFiltered,
|
||||||
e.Domain = "domain"
|
Time: 123456,
|
||||||
e.Client = "127.0.0.1"
|
})
|
||||||
e.Result = RNotFiltered
|
|
||||||
e.Time = 123456
|
|
||||||
s.Update(e)
|
|
||||||
|
|
||||||
d, ok := s.getData()
|
d, ok := s.getData()
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
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}
|
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))
|
assert.True(t, UIntArrayEquals(d.DNSQueries, a))
|
||||||
@@ -70,12 +73,15 @@ func TestStats(t *testing.T) {
|
|||||||
assert.True(t, UIntArrayEquals(d.ReplacedParental, a))
|
assert.True(t, UIntArrayEquals(d.ReplacedParental, a))
|
||||||
|
|
||||||
m := d.TopQueried
|
m := d.TopQueried
|
||||||
|
require.NotEmpty(t, m)
|
||||||
assert.EqualValues(t, 1, m[0]["domain"])
|
assert.EqualValues(t, 1, m[0]["domain"])
|
||||||
|
|
||||||
m = d.TopBlocked
|
m = d.TopBlocked
|
||||||
|
require.NotEmpty(t, m)
|
||||||
assert.EqualValues(t, 1, m[0]["domain"])
|
assert.EqualValues(t, 1, m[0]["domain"])
|
||||||
|
|
||||||
m = d.TopClients
|
m = d.TopClients
|
||||||
|
require.NotEmpty(t, m)
|
||||||
assert.EqualValues(t, 2, m[0]["127.0.0.1"])
|
assert.EqualValues(t, 2, m[0]["127.0.0.1"])
|
||||||
|
|
||||||
assert.EqualValues(t, 2, d.NumDNSQueries)
|
assert.EqualValues(t, 2, d.NumDNSQueries)
|
||||||
@@ -86,81 +92,69 @@ func TestStats(t *testing.T) {
|
|||||||
assert.EqualValues(t, 0.123456, d.AvgProcessingTime)
|
assert.EqualValues(t, 0.123456, d.AvgProcessingTime)
|
||||||
|
|
||||||
topClients := s.GetTopClientsIP(2)
|
topClients := s.GetTopClientsIP(2)
|
||||||
|
require.NotEmpty(t, topClients)
|
||||||
assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0]))
|
assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0]))
|
||||||
|
|
||||||
s.clear()
|
|
||||||
s.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLargeNumbers(t *testing.T) {
|
func TestLargeNumbers(t *testing.T) {
|
||||||
var hour int32 = 1
|
var hour int32 = 0
|
||||||
newID := func() uint32 {
|
newID := func() uint32 {
|
||||||
// use "atomic" to make Go race detector happy
|
// Use "atomic" to make go race detector happy.
|
||||||
return uint32(atomic.LoadInt32(&hour))
|
return uint32(atomic.LoadInt32(&hour))
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.SetLevel(log.DEBUG)
|
|
||||||
conf := Config{
|
conf := Config{
|
||||||
Filename: "./stats.db",
|
Filename: "./stats.db",
|
||||||
LimitDays: 1,
|
LimitDays: 1,
|
||||||
UnitID: newID,
|
UnitID: newID,
|
||||||
}
|
}
|
||||||
|
s, err := createObject(conf)
|
||||||
|
require.Nil(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
s.Close()
|
||||||
assert.Nil(t, os.Remove(conf.Filename))
|
assert.Nil(t, os.Remove(conf.Filename))
|
||||||
})
|
})
|
||||||
|
|
||||||
s, _ := createObject(conf)
|
// Number of distinct clients and domains every hour.
|
||||||
e := Entry{}
|
const n = 1000
|
||||||
|
|
||||||
n := 1000 // number of distinct clients and domains every hour
|
for h := 0; h < 12; h++ {
|
||||||
for h := 0; h != 12; h++ {
|
atomic.AddInt32(&hour, 1)
|
||||||
if h != 0 {
|
for i := 0; i < n; i++ {
|
||||||
atomic.AddInt32(&hour, 1)
|
s.Update(Entry{
|
||||||
}
|
Domain: fmt.Sprintf("domain%d", i),
|
||||||
for i := 0; i != n; i++ {
|
Client: net.IP{
|
||||||
e.Domain = fmt.Sprintf("domain%d", i)
|
127,
|
||||||
ip := net.IP{127, 0, 0, 1}
|
0,
|
||||||
ip[2] = byte((i & 0xff00) >> 8)
|
byte((i & 0xff00) >> 8),
|
||||||
ip[3] = byte(i & 0xff)
|
byte(i & 0xff),
|
||||||
e.Client = ip.String()
|
}.String(),
|
||||||
e.Result = RNotFiltered
|
Result: RNotFiltered,
|
||||||
e.Time = 123456
|
Time: 123456,
|
||||||
s.Update(e)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, ok := s.getData()
|
d, ok := s.getData()
|
||||||
assert.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.EqualValues(t, int(hour)*n, d.NumDNSQueries)
|
assert.EqualValues(t, hour*n, d.NumDNSQueries)
|
||||||
|
|
||||||
s.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this code is a chunk copied from getData() that generates aggregate data per day
|
func TestStatsCollector(t *testing.T) {
|
||||||
func aggregateDataPerDay(firstID uint32) int {
|
ng := func(_ *unitDB) uint64 {
|
||||||
firstDayID := (firstID + 24 - 1) / 24 * 24 // align_ceil(24)
|
return 0
|
||||||
a := []uint64{}
|
}
|
||||||
var sum uint64
|
units := make([]*unitDB, 720)
|
||||||
id := firstDayID
|
|
||||||
nextDayID := firstDayID + 24
|
t.Run("hours", func(t *testing.T) {
|
||||||
for i := firstDayID - firstID; int(i) != 720; i++ {
|
statsData := statsCollector(units, 0, Hours, ng)
|
||||||
sum++
|
assert.Len(t, statsData, 720)
|
||||||
if id == nextDayID {
|
})
|
||||||
a = append(a, sum)
|
|
||||||
sum = 0
|
t.Run("days", func(t *testing.T) {
|
||||||
nextDayID += 24
|
for i := 0; i != 25; i++ {
|
||||||
|
statsData := statsCollector(units, uint32(i), Days, ng)
|
||||||
|
require.Lenf(t, statsData, 30, "i=%d", i)
|
||||||
}
|
}
|
||||||
id++
|
})
|
||||||
}
|
|
||||||
if id <= nextDayID {
|
|
||||||
a = append(a, sum)
|
|
||||||
}
|
|
||||||
return len(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAggregateDataPerTimeUnit(t *testing.T) {
|
|
||||||
for i := 0; i != 25; i++ {
|
|
||||||
alen := aggregateDataPerDay(uint32(i))
|
|
||||||
assert.Equalf(t, 30, alen, "i=%d", i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,10 +12,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(a.garipov): Rewrite all of this. Add proper error handling and
|
||||||
|
// inspection. Improve logging. Decrease complexity.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxDomains = 100 // max number of top domains to store in file or return via Get()
|
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()
|
maxClients = 100 // max number of top clients to store in file or return via Get()
|
||||||
@@ -61,11 +66,12 @@ type unitDB struct {
|
|||||||
TimeAvg uint32 // usec
|
TimeAvg uint32 // usec
|
||||||
}
|
}
|
||||||
|
|
||||||
func createObject(conf Config) (*statsCtx, error) {
|
func createObject(conf Config) (s *statsCtx, err error) {
|
||||||
s := statsCtx{}
|
s = &statsCtx{}
|
||||||
if !checkInterval(conf.LimitDays) {
|
if !checkInterval(conf.LimitDays) {
|
||||||
conf.LimitDays = 1
|
conf.LimitDays = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf = &Config{}
|
s.conf = &Config{}
|
||||||
*s.conf = conf
|
*s.conf = conf
|
||||||
s.conf.limit = conf.LimitDays * 24
|
s.conf.limit = conf.LimitDays * 24
|
||||||
@@ -84,27 +90,43 @@ func createObject(conf Config) (*statsCtx, error) {
|
|||||||
log.Tracef("Deleting old units...")
|
log.Tracef("Deleting old units...")
|
||||||
firstID := id - s.conf.limit - 1
|
firstID := id - s.conf.limit - 1
|
||||||
unitDel := 0
|
unitDel := 0
|
||||||
forEachBkt := func(name []byte, b *bolt.Bucket) error {
|
|
||||||
id := uint32(btoi(name))
|
// TODO(a.garipov): See if this is actually necessary. Looks
|
||||||
if id < firstID {
|
// like a rather bizarre solution.
|
||||||
err := tx.DeleteBucket(name)
|
errStop := agherr.Error("stop iteration")
|
||||||
if err != nil {
|
forEachBkt := func(name []byte, _ *bolt.Bucket) (cberr error) {
|
||||||
log.Debug("tx.DeleteBucket: %s", err)
|
nameID := uint32(btoi(name))
|
||||||
|
if nameID < firstID {
|
||||||
|
cberr = tx.DeleteBucket(name)
|
||||||
|
if cberr != nil {
|
||||||
|
log.Debug("stats: tx.DeleteBucket: %s", cberr)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
log.Debug("Stats: deleted unit %d", id)
|
|
||||||
|
log.Debug("stats: deleted unit %d", nameID)
|
||||||
unitDel++
|
unitDel++
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("")
|
|
||||||
|
return errStop
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.ForEach(forEachBkt)
|
||||||
|
if err != nil && !errors.Is(err, errStop) {
|
||||||
|
log.Debug("stats: deleting units: %s", err)
|
||||||
}
|
}
|
||||||
_ = tx.ForEach(forEachBkt)
|
|
||||||
|
|
||||||
udb = s.loadUnitFromDB(tx, id)
|
udb = s.loadUnitFromDB(tx, id)
|
||||||
|
|
||||||
if unitDel != 0 {
|
if unitDel != 0 {
|
||||||
s.commitTxn(tx)
|
s.commitTxn(tx)
|
||||||
} else {
|
} else {
|
||||||
_ = tx.Rollback()
|
err = tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("rolling back: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +137,9 @@ func createObject(conf Config) (*statsCtx, error) {
|
|||||||
}
|
}
|
||||||
s.unit = &u
|
s.unit = &u
|
||||||
|
|
||||||
log.Debug("Stats: initialized")
|
log.Debug("stats: initialized")
|
||||||
return &s, nil
|
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsCtx) Start() {
|
func (s *statsCtx) Start() {
|
||||||
@@ -133,7 +156,7 @@ func (s *statsCtx) dbOpen() bool {
|
|||||||
log.Tracef("db.Open...")
|
log.Tracef("db.Open...")
|
||||||
s.db, err = bolt.Open(s.conf.Filename, 0o644, nil)
|
s.db, err = bolt.Open(s.conf.Filename, 0o644, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Stats: open DB: %s: %s", s.conf.Filename, err)
|
log.Error("stats: open DB: %s: %s", s.conf.Filename, err)
|
||||||
if err.Error() == "invalid argument" {
|
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/internal/wiki/Getting-Started#limitations")
|
log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#limitations")
|
||||||
}
|
}
|
||||||
@@ -262,10 +285,13 @@ func (s *statsCtx) periodicFlush() {
|
|||||||
func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
|
func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
|
||||||
err := tx.DeleteBucket(unitName(id))
|
err := tx.DeleteBucket(unitName(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("bolt DeleteBucket: %s", err)
|
log.Tracef("stats: bolt DeleteBucket: %s", err)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Debug("Stats: deleted unit %d", id)
|
|
||||||
|
log.Debug("stats: deleted unit %d", id)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +416,7 @@ func (s *statsCtx) setLimit(limitDays int) {
|
|||||||
conf := *s.conf
|
conf := *s.conf
|
||||||
conf.limit = uint32(limitDays) * 24
|
conf.limit = uint32(limitDays) * 24
|
||||||
s.conf = &conf
|
s.conf = &conf
|
||||||
log.Debug("Stats: set limit: %d", limitDays)
|
log.Debug("stats: set limit: %d", limitDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
|
func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
|
||||||
@@ -415,7 +441,7 @@ func (s *statsCtx) Close() {
|
|||||||
log.Tracef("db.Close")
|
log.Tracef("db.Close")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Stats: closed")
|
log.Debug("stats: closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset counters and clear database
|
// Reset counters and clear database
|
||||||
@@ -443,7 +469,7 @@ func (s *statsCtx) clear() {
|
|||||||
|
|
||||||
_ = s.dbOpen()
|
_ = s.dbOpen()
|
||||||
|
|
||||||
log.Debug("Stats: cleared")
|
log.Debug("stats: cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Client IP address
|
// Get Client IP address
|
||||||
@@ -528,6 +554,57 @@ func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) {
|
|||||||
return units, firstID
|
return units, firstID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// numsGetter is a signature for statsCollector argument.
|
||||||
|
type numsGetter func(u *unitDB) (num uint64)
|
||||||
|
|
||||||
|
// statsCollector collects statisctics for the given *unitDB slice by specified
|
||||||
|
// timeUnit using ng to retrieve data.
|
||||||
|
func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsGetter) (nums []uint64) {
|
||||||
|
if timeUnit == Hours {
|
||||||
|
for _, u := range units {
|
||||||
|
nums = append(nums, ng(u))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Per time unit counters: 720 hours may span 31 days, so we
|
||||||
|
// skip data for the first day in this case.
|
||||||
|
// align_ceil(24)
|
||||||
|
firstDayID := (firstID + 24 - 1) / 24 * 24
|
||||||
|
|
||||||
|
var sum uint64
|
||||||
|
id := firstDayID
|
||||||
|
nextDayID := firstDayID + 24
|
||||||
|
for i := int(firstDayID - firstID); i != len(units); i++ {
|
||||||
|
sum += ng(units[i])
|
||||||
|
if id == nextDayID {
|
||||||
|
nums = append(nums, sum)
|
||||||
|
sum = 0
|
||||||
|
nextDayID += 24
|
||||||
|
}
|
||||||
|
id++
|
||||||
|
}
|
||||||
|
if id <= nextDayID {
|
||||||
|
nums = append(nums, sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nums
|
||||||
|
}
|
||||||
|
|
||||||
|
// pairsGetter is a signature for topsCollector argument.
|
||||||
|
type pairsGetter func(u *unitDB) (pairs []countPair)
|
||||||
|
|
||||||
|
// topsCollector collects statistics about highest values fro 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a2 := convertMapToSlice(m, max)
|
||||||
|
return convertTopSlice(a2)
|
||||||
|
}
|
||||||
|
|
||||||
/* Algorithm:
|
/* Algorithm:
|
||||||
. Prepare array of N units, where N is the value of "limit" configuration setting
|
. Prepare array of N units, where N is the value of "limit" configuration setting
|
||||||
. Load data for the most recent units from file
|
. Load data for the most recent units from file
|
||||||
@@ -568,65 +645,25 @@ func (s *statsCtx) getData() (statsResponse, bool) {
|
|||||||
return statsResponse{}, false
|
return statsResponse{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// per time unit counters:
|
dnsQueries := statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NTotal })
|
||||||
// 720 hours may span 31 days, so we skip data for the first day in this case
|
|
||||||
firstDayID := (firstID + 24 - 1) / 24 * 24 // align_ceil(24)
|
|
||||||
|
|
||||||
statsCollector := func(numsGetter func(u *unitDB) (num uint64)) (nums []uint64) {
|
|
||||||
if timeUnit == Hours {
|
|
||||||
for _, u := range units {
|
|
||||||
nums = append(nums, numsGetter(u))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var sum uint64
|
|
||||||
id := firstDayID
|
|
||||||
nextDayID := firstDayID + 24
|
|
||||||
for i := int(firstDayID - firstID); i != len(units); i++ {
|
|
||||||
sum += numsGetter(units[i])
|
|
||||||
if id == nextDayID {
|
|
||||||
nums = append(nums, sum)
|
|
||||||
sum = 0
|
|
||||||
nextDayID += 24
|
|
||||||
}
|
|
||||||
id++
|
|
||||||
}
|
|
||||||
if id <= nextDayID {
|
|
||||||
nums = append(nums, sum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nums
|
|
||||||
}
|
|
||||||
|
|
||||||
topsCollector := func(max int, pairsGetter func(u *unitDB) (pairs []countPair)) []map[string]uint64 {
|
|
||||||
m := map[string]uint64{}
|
|
||||||
for _, u := range units {
|
|
||||||
for _, it := range pairsGetter(u) {
|
|
||||||
m[it.Name] += it.Count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a2 := convertMapToSlice(m, max)
|
|
||||||
return convertTopSlice(a2)
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsQueries := statsCollector(func(u *unitDB) (num uint64) { return u.NTotal })
|
|
||||||
if timeUnit != Hours && len(dnsQueries) != int(limit/24) {
|
if timeUnit != Hours && len(dnsQueries) != int(limit/24) {
|
||||||
log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit)
|
log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := statsResponse{
|
data := statsResponse{
|
||||||
DNSQueries: dnsQueries,
|
DNSQueries: dnsQueries,
|
||||||
BlockedFiltering: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }),
|
BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }),
|
||||||
ReplacedSafebrowsing: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }),
|
ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }),
|
||||||
ReplacedParental: statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
|
ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
|
||||||
TopQueried: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }),
|
TopQueried: topsCollector(units, maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }),
|
||||||
TopBlocked: topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
|
TopBlocked: topsCollector(units, maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
|
||||||
TopClients: topsCollector(maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }),
|
TopClients: topsCollector(units, maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }),
|
||||||
}
|
}
|
||||||
|
|
||||||
// total counters:
|
// Total counters:
|
||||||
|
sum := unitDB{
|
||||||
sum := unitDB{}
|
NResult: make([]uint64, rLast),
|
||||||
sum.NResult = make([]uint64, rLast)
|
}
|
||||||
timeN := 0
|
timeN := 0
|
||||||
for _, u := range units {
|
for _, u := range units {
|
||||||
sum.NTotal += u.NTotal
|
sum.NTotal += u.NTotal
|
||||||
|
|||||||
@@ -5,10 +5,17 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
|
||||||
|
// the IP being static is available.
|
||||||
|
const ErrNoStaticIPInfo agherr.Error = "no information about static ip"
|
||||||
|
|
||||||
// IfaceHasStaticIP checks if interface is configured to have static IP address.
|
// IfaceHasStaticIP checks if interface is configured to have static IP address.
|
||||||
|
// If it can't give a definitive answer, it returns false and an error for which
|
||||||
|
// errors.Is(err, ErrNoStaticIPInfo) is true.
|
||||||
func IfaceHasStaticIP(ifaceName string) (has bool, err error) {
|
func IfaceHasStaticIP(ifaceName string) (has bool, err error) {
|
||||||
return ifaceHasStaticIP(ifaceName)
|
return ifaceHasStaticIP(ifaceName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ import (
|
|||||||
const maxConfigFileSize = 1024 * 1024
|
const maxConfigFileSize = 1024 * 1024
|
||||||
|
|
||||||
func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
||||||
var f *os.File
|
// 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.
|
||||||
|
|
||||||
for _, check := range []struct {
|
for _, check := range []struct {
|
||||||
checker func(io.Reader, string) (bool, error)
|
checker func(io.Reader, string) (bool, error)
|
||||||
filePath string
|
filePath string
|
||||||
@@ -32,28 +36,37 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
|||||||
checker: ifacesStaticConfig,
|
checker: ifacesStaticConfig,
|
||||||
filePath: "/etc/network/interfaces",
|
filePath: "/etc/network/interfaces",
|
||||||
}} {
|
}} {
|
||||||
|
var f *os.File
|
||||||
f, err = os.Open(check.filePath)
|
f, err = os.Open(check.filePath)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// ErrNotExist can happen here if there is no such file.
|
||||||
|
// This is normal, as not every system uses those files.
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
fileReadCloser, err := aghio.LimitReadCloser(f, maxConfigFileSize)
|
var fileReadCloser io.ReadCloser
|
||||||
|
fileReadCloser, err = aghio.LimitReadCloser(f, maxConfigFileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer fileReadCloser.Close()
|
defer fileReadCloser.Close()
|
||||||
|
|
||||||
has, err = check.checker(fileReadCloser, ifaceName)
|
has, err = check.checker(fileReadCloser, ifaceName)
|
||||||
if has || err != nil {
|
if err != nil {
|
||||||
break
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return has, err
|
return false, ErrNoStaticIPInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
|
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const nl = "\n"
|
const nl = "\n"
|
||||||
@@ -48,7 +49,7 @@ func TestDHCPCDStaticConfig(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := bytes.NewReader(tc.data)
|
r := bytes.NewReader(tc.data)
|
||||||
has, err := dhcpcdStaticConfig(r, "wlan0")
|
has, err := dhcpcdStaticConfig(r, "wlan0")
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, tc.want, has)
|
assert.Equal(t, tc.want, has)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,26 +86,36 @@ func TestIfacesStaticConfig(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := bytes.NewReader(tc.data)
|
r := bytes.NewReader(tc.data)
|
||||||
has, err := ifacesStaticConfig(r, "enp0s3")
|
has, err := ifacesStaticConfig(r, "enp0s3")
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, tc.want, has)
|
assert.Equal(t, tc.want, has)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetStaticIPdhcpcdConf(t *testing.T) {
|
func TestSetStaticIPdhcpcdConf(t *testing.T) {
|
||||||
dhcpcdConf := nl + `interface wlan0` + nl +
|
testCases := []struct {
|
||||||
`static ip_address=192.168.0.2/24` + nl +
|
name string
|
||||||
`static routers=192.168.0.1` + nl +
|
dhcpcdConf string
|
||||||
`static domain_name_servers=192.168.0.2` + nl + nl
|
routers net.IP
|
||||||
|
}{{
|
||||||
|
name: "with_gateway",
|
||||||
|
dhcpcdConf: 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},
|
||||||
|
}, {
|
||||||
|
name: "without_gateway",
|
||||||
|
dhcpcdConf: 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,
|
||||||
|
}}
|
||||||
|
|
||||||
s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", net.IP{192, 168, 0, 1}, net.IP{192, 168, 0, 2})
|
for _, tc := range testCases {
|
||||||
assert.Equal(t, dhcpcdConf, s)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", tc.routers, net.IP{192, 168, 0, 2})
|
||||||
// without gateway
|
assert.Equal(t, tc.dhcpcdConf, s)
|
||||||
dhcpcdConf = nl + `interface wlan0` + nl +
|
})
|
||||||
`static ip_address=192.168.0.2/24` + nl +
|
}
|
||||||
`static domain_name_servers=192.168.0.2` + nl + nl
|
|
||||||
|
|
||||||
s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", nil, net.IP{192, 168, 0, 2})
|
|
||||||
assert.Equal(t, dhcpcdConf, s)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,114 +11,162 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
aghtest.DiscardLogOutput(m)
|
aghtest.DiscardLogOutput(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestDir() string {
|
func prepareTestFile(t *testing.T) (f *os.File) {
|
||||||
const dir = "./agh-test"
|
t.Helper()
|
||||||
_ = os.RemoveAll(dir)
|
|
||||||
_ = os.MkdirAll(dir, 0o755)
|
dir := aghtest.PrepareTestDir(t)
|
||||||
return dir
|
|
||||||
|
f, err := ioutil.TempFile(dir, "")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, f)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.Nil(t, f.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertWriting(t *testing.T, f *os.File, strs ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for _, str := range strs {
|
||||||
|
n, err := f.WriteString(str)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, n, len(str))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAutoHostsResolution(t *testing.T) {
|
func TestAutoHostsResolution(t *testing.T) {
|
||||||
ah := AutoHosts{}
|
ah := &AutoHosts{}
|
||||||
|
|
||||||
dir := prepareTestDir()
|
f := prepareTestFile(t)
|
||||||
defer func() { _ = os.RemoveAll(dir) }()
|
|
||||||
|
|
||||||
f, _ := ioutil.TempFile(dir, "")
|
|
||||||
defer func() { _ = os.Remove(f.Name()) }()
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, _ = f.WriteString(" 127.0.0.1 host localhost # comment \n")
|
|
||||||
_, _ = f.WriteString(" ::1 localhost#comment \n")
|
|
||||||
|
|
||||||
|
assertWriting(t, f,
|
||||||
|
" 127.0.0.1 host localhost # comment \n",
|
||||||
|
" ::1 localhost#comment \n",
|
||||||
|
)
|
||||||
ah.Init(f.Name())
|
ah.Init(f.Name())
|
||||||
|
|
||||||
// Existing host
|
t.Run("existing_host", func(t *testing.T) {
|
||||||
ips := ah.Process("localhost", dns.TypeA)
|
ips := ah.Process("localhost", dns.TypeA)
|
||||||
assert.NotNil(t, ips)
|
require.Len(t, ips, 1)
|
||||||
assert.Len(t, ips, 1)
|
assert.Equal(t, net.IPv4(127, 0, 0, 1), ips[0])
|
||||||
assert.Equal(t, net.ParseIP("127.0.0.1"), ips[0])
|
})
|
||||||
|
|
||||||
// Unknown host
|
t.Run("unknown_host", func(t *testing.T) {
|
||||||
ips = ah.Process("newhost", dns.TypeA)
|
ips := ah.Process("newhost", dns.TypeA)
|
||||||
assert.Nil(t, ips)
|
assert.Nil(t, ips)
|
||||||
|
|
||||||
// Unknown host (comment)
|
// Comment.
|
||||||
ips = ah.Process("comment", dns.TypeA)
|
ips = ah.Process("comment", dns.TypeA)
|
||||||
assert.Nil(t, ips)
|
assert.Nil(t, ips)
|
||||||
|
})
|
||||||
|
|
||||||
// Test hosts file
|
t.Run("hosts_file", func(t *testing.T) {
|
||||||
table := ah.List()
|
names, ok := ah.List()["127.0.0.1"]
|
||||||
names, ok := table["127.0.0.1"]
|
require.True(t, ok)
|
||||||
assert.True(t, ok)
|
assert.Equal(t, []string{"host", "localhost"}, names)
|
||||||
assert.Equal(t, []string{"host", "localhost"}, names)
|
})
|
||||||
|
|
||||||
// Test PTR
|
t.Run("ptr", func(t *testing.T) {
|
||||||
a, _ := dns.ReverseAddr("127.0.0.1")
|
testCases := []struct {
|
||||||
a = strings.TrimSuffix(a, ".")
|
wantIP string
|
||||||
hosts := ah.ProcessReverse(a, dns.TypePTR)
|
wantLen int
|
||||||
if assert.Len(t, hosts, 2) {
|
wantHost string
|
||||||
assert.Equal(t, hosts[0], "host")
|
}{
|
||||||
}
|
{wantIP: "127.0.0.1", wantLen: 2, wantHost: "host"},
|
||||||
|
{wantIP: "::1", wantLen: 1, wantHost: "localhost"},
|
||||||
|
}
|
||||||
|
|
||||||
a, _ = dns.ReverseAddr("::1")
|
for _, tc := range testCases {
|
||||||
a = strings.TrimSuffix(a, ".")
|
a, err := dns.ReverseAddr(tc.wantIP)
|
||||||
hosts = ah.ProcessReverse(a, dns.TypePTR)
|
require.Nil(t, err)
|
||||||
if assert.Len(t, hosts, 1) {
|
|
||||||
assert.Equal(t, hosts[0], "localhost")
|
a = strings.TrimSuffix(a, ".")
|
||||||
}
|
hosts := ah.ProcessReverse(a, dns.TypePTR)
|
||||||
|
require.Len(t, hosts, tc.wantLen)
|
||||||
|
assert.Equal(t, tc.wantHost, hosts[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAutoHostsFSNotify(t *testing.T) {
|
func TestAutoHostsFSNotify(t *testing.T) {
|
||||||
ah := AutoHosts{}
|
ah := &AutoHosts{}
|
||||||
|
|
||||||
dir := prepareTestDir()
|
f := prepareTestFile(t)
|
||||||
defer func() { _ = os.RemoveAll(dir) }()
|
|
||||||
|
|
||||||
f, _ := ioutil.TempFile(dir, "")
|
assertWriting(t, f, " 127.0.0.1 host localhost \n")
|
||||||
defer func() { _ = os.Remove(f.Name()) }()
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Init
|
|
||||||
_, _ = f.WriteString(" 127.0.0.1 host localhost \n")
|
|
||||||
ah.Init(f.Name())
|
ah.Init(f.Name())
|
||||||
|
|
||||||
// Unknown host
|
t.Run("unknown_host", func(t *testing.T) {
|
||||||
ips := ah.Process("newhost", dns.TypeA)
|
ips := ah.Process("newhost", dns.TypeA)
|
||||||
assert.Nil(t, ips)
|
assert.Nil(t, ips)
|
||||||
|
})
|
||||||
|
|
||||||
// Stat monitoring for changes
|
// Start monitoring for changes.
|
||||||
ah.Start()
|
ah.Start()
|
||||||
defer ah.Close()
|
t.Cleanup(ah.Close)
|
||||||
|
|
||||||
// Update file
|
assertWriting(t, f, "127.0.0.2 newhost\n")
|
||||||
_, _ = f.WriteString("127.0.0.2 newhost\n")
|
require.Nil(t, f.Sync())
|
||||||
_ = f.Sync()
|
|
||||||
|
|
||||||
// wait until fsnotify has triggerred and processed the file-modification event
|
// Wait until fsnotify has triggerred and processed the
|
||||||
|
// file-modification event.
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
// Check if we are notified about changes
|
t.Run("notified", func(t *testing.T) {
|
||||||
ips = ah.Process("newhost", dns.TypeA)
|
ips := ah.Process("newhost", dns.TypeA)
|
||||||
assert.NotNil(t, ips)
|
assert.NotNil(t, ips)
|
||||||
assert.Len(t, ips, 1)
|
require.Len(t, ips, 1)
|
||||||
assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0]))
|
assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0]))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIP(t *testing.T) {
|
func TestDNSReverseAddr(t *testing.T) {
|
||||||
assert.True(t, net.IP{127, 0, 0, 1}.Equal(DNSUnreverseAddr("1.0.0.127.in-addr.arpa")))
|
testCases := []struct {
|
||||||
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())
|
name string
|
||||||
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())
|
have string
|
||||||
|
want net.IP
|
||||||
|
}{{
|
||||||
|
name: "good_ipv4",
|
||||||
|
have: "1.0.0.127.in-addr.arpa",
|
||||||
|
want: net.IP{127, 0, 0, 1},
|
||||||
|
}, {
|
||||||
|
name: "good_ipv6",
|
||||||
|
have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||||
|
want: net.ParseIP("::abcd:1234"),
|
||||||
|
}, {
|
||||||
|
name: "good_ipv6_case",
|
||||||
|
have: "4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||||
|
want: net.ParseIP("::abcd:1234"),
|
||||||
|
}, {
|
||||||
|
name: "bad_ipv4_dot",
|
||||||
|
have: "1.0.0.127.in-addr.arpa.",
|
||||||
|
}, {
|
||||||
|
name: "wrong_ipv4",
|
||||||
|
have: ".0.0.127.in-addr.arpa",
|
||||||
|
}, {
|
||||||
|
name: "wrong_ipv6",
|
||||||
|
have: ".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||||
|
}, {
|
||||||
|
name: "bad_ipv6_dot",
|
||||||
|
have: "4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa",
|
||||||
|
}, {
|
||||||
|
name: "bad_ipv6_space",
|
||||||
|
have: "4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||||
|
}}
|
||||||
|
|
||||||
assert.Nil(t, DNSUnreverseAddr("1.0.0.127.in-addr.arpa."))
|
for _, tc := range testCases {
|
||||||
assert.Nil(t, DNSUnreverseAddr(".0.0.127.in-addr.arpa"))
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
assert.Nil(t, DNSUnreverseAddr(".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
|
ip := DNSUnreverseAddr(tc.have)
|
||||||
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa"))
|
assert.True(t, tc.want.Equal(ip))
|
||||||
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSplitNext(t *testing.T) {
|
func TestSplitNext(t *testing.T) {
|
||||||
s := " a,b , c "
|
s := " a,b , c "
|
||||||
|
|
||||||
assert.Equal(t, "a", SplitNext(&s, ','))
|
assert.Equal(t, "a", SplitNext(&s, ','))
|
||||||
assert.Equal(t, "b", SplitNext(&s, ','))
|
assert.Equal(t, "b", SplitNext(&s, ','))
|
||||||
assert.Equal(t, "c", SplitNext(&s, ','))
|
assert.Equal(t, "c", SplitNext(&s, ','))
|
||||||
assert.Empty(t, s)
|
require.Empty(t, s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,15 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
||||||
ifaces, err := GetValidNetInterfacesForWeb()
|
ifaces, err := GetValidNetInterfacesForWeb()
|
||||||
if err != nil {
|
require.Nilf(t, err, "Cannot get net interfaces: %s", err)
|
||||||
t.Fatalf("Cannot get net interfaces: %s", err)
|
require.NotEmpty(t, ifaces, "No net interfaces found")
|
||||||
}
|
|
||||||
if len(ifaces) == 0 {
|
|
||||||
t.Fatalf("No net interfaces found")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
if len(iface.Addresses) == 0 {
|
require.NotEmptyf(t, iface.Addresses, "No addresses found for %s", iface.Name)
|
||||||
t.Fatalf("No addresses found for %s", iface.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("%v", iface)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
## v0.105: API changes
|
## v0.105: API changes
|
||||||
|
|
||||||
|
### New `"client_id"` field in `GET /querylog` response
|
||||||
|
|
||||||
|
* The new field `"client_id"` of `QueryLogItem` objects is the ID sent by the
|
||||||
|
client for encrypted requests, if there was any. See the
|
||||||
|
"[Identifying clients]" section of our wiki.
|
||||||
|
|
||||||
### New `"dnscrypt"` `"client_proto"` value in `GET /querylog` response
|
### New `"dnscrypt"` `"client_proto"` value in `GET /querylog` response
|
||||||
|
|
||||||
* The field `"client_proto"` can now have the value `"dnscrypt"` when the
|
* The field `"client_proto"` can now have the value `"dnscrypt"` when the
|
||||||
@@ -69,6 +75,8 @@
|
|||||||
|
|
||||||
As well as other documentation fixes.
|
As well as other documentation fixes.
|
||||||
|
|
||||||
|
[Identifying clients]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#idclient
|
||||||
|
|
||||||
## v0.103: API changes
|
## v0.103: API changes
|
||||||
|
|
||||||
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
||||||
|
|||||||
@@ -228,7 +228,13 @@ main() {
|
|||||||
|
|
||||||
download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package"
|
download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package"
|
||||||
|
|
||||||
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
if [ "${OS}" = "darwin" ]; then
|
||||||
|
# TODO: remove this after v0.106.0 release
|
||||||
|
mkdir "${AGH_DIR}"
|
||||||
|
unpack "${PKG_NAME}" "${AGH_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||||
|
else
|
||||||
|
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||||
|
fi
|
||||||
|
|
||||||
# Install AdGuard Home service and run it.
|
# Install AdGuard Home service and run it.
|
||||||
( cd "${AGH_DIR}" && ./AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" )
|
( cd "${AGH_DIR}" && ./AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" )
|
||||||
|
|||||||
@@ -17,7 +17,15 @@ set -e -f -u
|
|||||||
readonly channel="$CHANNEL"
|
readonly channel="$CHANNEL"
|
||||||
readonly commit="$COMMIT"
|
readonly commit="$COMMIT"
|
||||||
readonly dist_dir="$DIST_DIR"
|
readonly dist_dir="$DIST_DIR"
|
||||||
readonly version="$VERSION"
|
|
||||||
|
if [ "${VERSION:-}" = 'v0.0.0' -o "${VERSION:-}" = '' ]
|
||||||
|
then
|
||||||
|
readonly version="$(sh ./scripts/make/version.sh)"
|
||||||
|
else
|
||||||
|
readonly version="$VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $version
|
||||||
|
|
||||||
# Allow users to use sudo.
|
# Allow users to use sudo.
|
||||||
readonly sudo_cmd="${SUDO:-}"
|
readonly sudo_cmd="${SUDO:-}"
|
||||||
|
|||||||
@@ -80,10 +80,18 @@ const request = (url, locale) => (
|
|||||||
return `${locale} - Not OK`;
|
return `${locale} - Not OK`;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep.
|
||||||
|
* @param {number} ms
|
||||||
|
*/
|
||||||
|
const sleep = (ms) => new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download locales
|
* Download locales
|
||||||
*/
|
*/
|
||||||
const download = () => {
|
const download = async () => {
|
||||||
const locales = LOCALES_LIST;
|
const locales = LOCALES_LIST;
|
||||||
|
|
||||||
if (!TWOSKY_URI) {
|
if (!TWOSKY_URI) {
|
||||||
@@ -91,10 +99,16 @@ const download = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requests = locales.map((locale) => {
|
const requests = [];
|
||||||
|
for (let i = 0; i < locales.length; i++) {
|
||||||
|
const locale = locales[i];
|
||||||
const url = getRequestUrl(locale, TWOSKY_URI, TWOSKY_PROJECT_ID);
|
const url = getRequestUrl(locale, TWOSKY_URI, TWOSKY_PROJECT_ID);
|
||||||
return request(url, locale);
|
requests.push(request(url, locale));
|
||||||
});
|
|
||||||
|
// Don't request the Crowdin API too aggressively to prevent spurious
|
||||||
|
// 400 errors.
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all(requests)
|
.all(requests)
|
||||||
|
|||||||
466
scripts/translations/package-lock.json
generated
466
scripts/translations/package-lock.json
generated
@@ -1,8 +1,466 @@
|
|||||||
{
|
{
|
||||||
"name": "translations",
|
"name": "translations",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"request": "^2.88.0",
|
||||||
|
"request-promise": "^4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv": {
|
||||||
|
"version": "6.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz",
|
||||||
|
"integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^2.0.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asn1": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/assert-plus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
|
},
|
||||||
|
"node_modules/aws-sign2": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
|
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws4": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||||
|
},
|
||||||
|
"node_modules/bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||||
|
"dependencies": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bluebird": {
|
||||||
|
"version": "3.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
|
||||||
|
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
|
||||||
|
},
|
||||||
|
"node_modules/caseless": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
|
},
|
||||||
|
"node_modules/dashdash": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ecc-jsbn": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||||
|
},
|
||||||
|
"node_modules/extsprintf": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||||
|
},
|
||||||
|
"node_modules/fast-json-stable-stringify": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||||
|
},
|
||||||
|
"node_modules/forever-agent": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.6",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/getpass": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/har-schema": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/har-validator": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^6.5.5",
|
||||||
|
"har-schema": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-signature": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"jsprim": "^1.2.2",
|
||||||
|
"sshpk": "^1.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8",
|
||||||
|
"npm": ">=1.3.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-typedarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||||
|
},
|
||||||
|
"node_modules/isstream": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||||
|
},
|
||||||
|
"node_modules/jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||||
|
},
|
||||||
|
"node_modules/json-schema": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||||
|
},
|
||||||
|
"node_modules/json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
|
},
|
||||||
|
"node_modules/json-stringify-safe": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
|
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||||
|
},
|
||||||
|
"node_modules/jsprim": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"extsprintf": "1.3.0",
|
||||||
|
"json-schema": "0.2.3",
|
||||||
|
"verror": "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.37.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
||||||
|
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
|
||||||
|
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "~1.37.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/oauth-sign": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
|
},
|
||||||
|
"node_modules/psl": {
|
||||||
|
"version": "1.1.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||||
|
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/request": {
|
||||||
|
"version": "2.88.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||||
|
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-sign2": "~0.7.0",
|
||||||
|
"aws4": "^1.8.0",
|
||||||
|
"caseless": "~0.12.0",
|
||||||
|
"combined-stream": "~1.0.6",
|
||||||
|
"extend": "~3.0.2",
|
||||||
|
"forever-agent": "~0.6.1",
|
||||||
|
"form-data": "~2.3.2",
|
||||||
|
"har-validator": "~5.1.0",
|
||||||
|
"http-signature": "~1.2.0",
|
||||||
|
"is-typedarray": "~1.0.0",
|
||||||
|
"isstream": "~0.1.2",
|
||||||
|
"json-stringify-safe": "~5.0.1",
|
||||||
|
"mime-types": "~2.1.19",
|
||||||
|
"oauth-sign": "~0.9.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
|
"qs": "~6.5.2",
|
||||||
|
"safe-buffer": "^5.1.2",
|
||||||
|
"tough-cookie": "~2.4.3",
|
||||||
|
"tunnel-agent": "^0.6.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/request-promise": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
|
||||||
|
"integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.0",
|
||||||
|
"request-promise-core": "1.1.1",
|
||||||
|
"stealthy-require": "^1.1.0",
|
||||||
|
"tough-cookie": ">=2.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/request-promise-core": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"node_modules/sshpk": {
|
||||||
|
"version": "1.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
|
||||||
|
"integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
|
||||||
|
"dependencies": {
|
||||||
|
"asn1": "~0.2.3",
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"bcrypt-pbkdf": "^1.0.0",
|
||||||
|
"dashdash": "^1.12.0",
|
||||||
|
"ecc-jsbn": "~0.1.1",
|
||||||
|
"getpass": "^0.1.1",
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.0.2",
|
||||||
|
"tweetnacl": "~0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/stealthy-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"psl": "^1.1.24",
|
||||||
|
"punycode": "^1.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||||
|
},
|
||||||
|
"node_modules/uri-js": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uri-js/node_modules/punycode": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/verror": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "^1.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.5.5",
|
"version": "6.5.5",
|
||||||
@@ -205,9 +663,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.37.0",
|
"version": "1.37.0",
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ initialisms = [
|
|||||||
, "MX"
|
, "MX"
|
||||||
, "PTR"
|
, "PTR"
|
||||||
, "QUIC"
|
, "QUIC"
|
||||||
|
, "RA"
|
||||||
, "SDNS"
|
, "SDNS"
|
||||||
|
, "SLAAC"
|
||||||
, "SVCB"
|
, "SVCB"
|
||||||
]
|
]
|
||||||
dot_import_whitelist = []
|
dot_import_whitelist = []
|
||||||
|
|||||||
Reference in New Issue
Block a user