Compare commits
2 Commits
v0.107.0-b
...
fix-stats-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02834e6b7b | ||
|
|
b45162a0f2 |
@@ -12,7 +12,6 @@
|
|||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fa": "فارسی",
|
"fa": "فارسی",
|
||||||
"fi": "Suomi",
|
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"hr": "Hrvatski",
|
"hr": "Hrvatski",
|
||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
@@ -34,7 +33,6 @@
|
|||||||
"sv": "Svenska",
|
"sv": "Svenska",
|
||||||
"th": "ภาษาไทย",
|
"th": "ภาษาไทย",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"uk": "Українська",
|
|
||||||
"vi": "Tiếng Việt",
|
"vi": "Tiếng Việt",
|
||||||
"zh-cn": "简体中文",
|
"zh-cn": "简体中文",
|
||||||
"zh-hk": "繁體中文(香港)",
|
"zh-hk": "繁體中文(香港)",
|
||||||
|
|||||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -10,12 +10,13 @@ and this project adheres to
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
## [v0.107.0] - 2021-11-02 (APPROX.)
|
## [v0.107.0] - 2021-09-28 (APPROX.)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Finnish and Ukrainian translations.
|
- DNS server IP addresses to the `mobileconfig` API responses ([#3568],
|
||||||
|
[#3607]).
|
||||||
- Setting the timeout for IP address pinging in the "Fastest IP address" mode
|
- Setting the timeout for IP address pinging in the "Fastest IP address" mode
|
||||||
through the new `fastest_timeout` field in the configuration file ([#1992]).
|
through the new `fastest_timeout` field in the configuration file ([#1992]).
|
||||||
- Static IP address detection on FreeBSD ([#3289]).
|
- Static IP address detection on FreeBSD ([#3289]).
|
||||||
@@ -47,13 +48,6 @@ and this project adheres to
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Better error message for ED25519 private keys, which are not widely supported
|
|
||||||
([#3737]).
|
|
||||||
- Cache now follows RFC more closely for negative answers ([#3707]).
|
|
||||||
- `$dnsrewrite` rules and other DNS rewrites will now be applied even when the
|
|
||||||
protection is disabled ([#1558]).
|
|
||||||
- DHCP gateway address, subnet mask, IP address range, and leases validations
|
|
||||||
([#3529]).
|
|
||||||
- The `systemd` service script will now create the `/var/log` directory when it
|
- The `systemd` service script will now create the `/var/log` directory when it
|
||||||
doesn't exist ([#3579]).
|
doesn't exist ([#3579]).
|
||||||
- Items in allowed clients, disallowed clients, and blocked hosts lists are now
|
- Items in allowed clients, disallowed clients, and blocked hosts lists are now
|
||||||
@@ -122,13 +116,7 @@ In this release, the schema version has changed from 10 to 12.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Incorrect `$dnsrewrite` results for entries from the operating system's hosts
|
- Adding an IP into only one of the matching ipsets on Linux ([#3638]).
|
||||||
file ([#3815]).
|
|
||||||
- Matching against rules with `|` at the end of the domain name ([#3371]).
|
|
||||||
- Incorrect assignment of explicitly configured DHCP options ([#3744]).
|
|
||||||
- Occasional panic during shutdown ([#3655]).
|
|
||||||
- Addition of IPs into only one as opposed to all matching ipsets on Linux
|
|
||||||
([#3638]).
|
|
||||||
- Removal of temporary filter files ([#3567]).
|
- Removal of temporary filter files ([#3567]).
|
||||||
- Panic when an upstream server responds with an empty question section
|
- Panic when an upstream server responds with an empty question section
|
||||||
([#3551]).
|
([#3551]).
|
||||||
@@ -164,7 +152,6 @@ In this release, the schema version has changed from 10 to 12.
|
|||||||
- Go 1.15 support.
|
- Go 1.15 support.
|
||||||
|
|
||||||
[#1381]: https://github.com/AdguardTeam/AdGuardHome/issues/1381
|
[#1381]: https://github.com/AdguardTeam/AdGuardHome/issues/1381
|
||||||
[#1558]: https://github.com/AdguardTeam/AdGuardHome/issues/1558
|
|
||||||
[#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691
|
[#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691
|
||||||
[#1898]: https://github.com/AdguardTeam/AdGuardHome/issues/1898
|
[#1898]: https://github.com/AdguardTeam/AdGuardHome/issues/1898
|
||||||
[#1992]: https://github.com/AdguardTeam/AdGuardHome/issues/1992
|
[#1992]: https://github.com/AdguardTeam/AdGuardHome/issues/1992
|
||||||
@@ -199,7 +186,6 @@ In this release, the schema version has changed from 10 to 12.
|
|||||||
[#3335]: https://github.com/AdguardTeam/AdGuardHome/issues/3335
|
[#3335]: https://github.com/AdguardTeam/AdGuardHome/issues/3335
|
||||||
[#3343]: https://github.com/AdguardTeam/AdGuardHome/issues/3343
|
[#3343]: https://github.com/AdguardTeam/AdGuardHome/issues/3343
|
||||||
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351
|
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351
|
||||||
[#3371]: https://github.com/AdguardTeam/AdGuardHome/issues/3371
|
|
||||||
[#3372]: https://github.com/AdguardTeam/AdGuardHome/issues/3372
|
[#3372]: https://github.com/AdguardTeam/AdGuardHome/issues/3372
|
||||||
[#3417]: https://github.com/AdguardTeam/AdGuardHome/issues/3417
|
[#3417]: https://github.com/AdguardTeam/AdGuardHome/issues/3417
|
||||||
[#3419]: https://github.com/AdguardTeam/AdGuardHome/issues/3419
|
[#3419]: https://github.com/AdguardTeam/AdGuardHome/issues/3419
|
||||||
@@ -209,7 +195,6 @@ In this release, the schema version has changed from 10 to 12.
|
|||||||
[#3450]: https://github.com/AdguardTeam/AdGuardHome/issues/3450
|
[#3450]: https://github.com/AdguardTeam/AdGuardHome/issues/3450
|
||||||
[#3457]: https://github.com/AdguardTeam/AdGuardHome/issues/3457
|
[#3457]: https://github.com/AdguardTeam/AdGuardHome/issues/3457
|
||||||
[#3506]: https://github.com/AdguardTeam/AdGuardHome/issues/3506
|
[#3506]: https://github.com/AdguardTeam/AdGuardHome/issues/3506
|
||||||
[#3529]: https://github.com/AdguardTeam/AdGuardHome/issues/3529
|
|
||||||
[#3538]: https://github.com/AdguardTeam/AdGuardHome/issues/3538
|
[#3538]: https://github.com/AdguardTeam/AdGuardHome/issues/3538
|
||||||
[#3551]: https://github.com/AdguardTeam/AdGuardHome/issues/3551
|
[#3551]: https://github.com/AdguardTeam/AdGuardHome/issues/3551
|
||||||
[#3564]: https://github.com/AdguardTeam/AdGuardHome/issues/3564
|
[#3564]: https://github.com/AdguardTeam/AdGuardHome/issues/3564
|
||||||
@@ -218,10 +203,6 @@ In this release, the schema version has changed from 10 to 12.
|
|||||||
[#3579]: https://github.com/AdguardTeam/AdGuardHome/issues/3579
|
[#3579]: https://github.com/AdguardTeam/AdGuardHome/issues/3579
|
||||||
[#3607]: https://github.com/AdguardTeam/AdGuardHome/issues/3607
|
[#3607]: https://github.com/AdguardTeam/AdGuardHome/issues/3607
|
||||||
[#3638]: https://github.com/AdguardTeam/AdGuardHome/issues/3638
|
[#3638]: https://github.com/AdguardTeam/AdGuardHome/issues/3638
|
||||||
[#3655]: https://github.com/AdguardTeam/AdGuardHome/issues/3655
|
|
||||||
[#3707]: https://github.com/AdguardTeam/AdGuardHome/issues/3707
|
|
||||||
[#3744]: https://github.com/AdguardTeam/AdGuardHome/issues/3744
|
|
||||||
[#3815]: https://github.com/AdguardTeam/AdGuardHome/issues/3815
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s
|
|||||||
|
|
||||||
* Beta channel builds
|
* Beta channel builds
|
||||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
||||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
||||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
|
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
|
||||||
* macOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
* macOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
||||||
@@ -294,7 +294,7 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s
|
|||||||
|
|
||||||
* Edge channel builds
|
* Edge channel builds
|
||||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
||||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
||||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
|
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
|
||||||
* macOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
* macOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# Make sure to sync any changes with the branch overrides below.
|
# Make sure to sync any changes with the branch overrides below.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'edge'
|
'channel': 'edge'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:3.6'
|
'dockerGo': 'adguard/golang-ubuntu:3.3'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Make release':
|
- 'Make release':
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
# need to build a few of these.
|
# need to build a few of these.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'beta'
|
'channel': 'beta'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:3.6'
|
'dockerGo': 'adguard/golang-ubuntu:3.3'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||||
# is built.
|
# is built.
|
||||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||||
@@ -276,4 +276,4 @@
|
|||||||
# are the ones that actually get released.
|
# are the ones that actually get released.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'release'
|
'channel': 'release'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:3.6'
|
'dockerGo': 'adguard/golang-ubuntu:3.3'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
'key': 'AHBRTSPECS'
|
'key': 'AHBRTSPECS'
|
||||||
'name': 'AdGuard Home - Build and run tests'
|
'name': 'AdGuard Home - Build and run tests'
|
||||||
'variables':
|
'variables':
|
||||||
'dockerGo': 'adguard/golang-ubuntu:3.6'
|
'dockerGo': 'adguard/golang-ubuntu:3.3'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Tests':
|
- 'Tests':
|
||||||
|
|||||||
@@ -37,9 +37,6 @@
|
|||||||
"dhcp_ipv6_settings": "Налады DHCP IPv6",
|
"dhcp_ipv6_settings": "Налады DHCP IPv6",
|
||||||
"form_error_required": "Абавязковае поле",
|
"form_error_required": "Абавязковае поле",
|
||||||
"form_error_ip4_format": "Няслушны фармат IPv4",
|
"form_error_ip4_format": "Няслушны фармат IPv4",
|
||||||
"form_error_ip4_range_start_format": "Няслушны IPv4-адрас пачатку дыяпазону",
|
|
||||||
"form_error_ip4_range_end_format": "Няслушны IPv4-адрас канца дыяпазону",
|
|
||||||
"form_error_ip4_gateway_format": "Няслушны IPv4-адрас шлюза",
|
|
||||||
"form_error_ip6_format": "Няслушны фармат IPv6",
|
"form_error_ip6_format": "Няслушны фармат IPv6",
|
||||||
"form_error_ip_format": "Няслушны фармат IP-адраса",
|
"form_error_ip_format": "Няслушны фармат IP-адраса",
|
||||||
"form_error_mac_format": "Некарэктны фармат MAC",
|
"form_error_mac_format": "Некарэктны фармат MAC",
|
||||||
@@ -48,12 +45,7 @@
|
|||||||
"form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
|
"form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
|
||||||
"form_error_positive": "Павінна быць больш 0",
|
"form_error_positive": "Павінна быць больш 0",
|
||||||
"form_error_negative": "Павінна быць не менш 0",
|
"form_error_negative": "Павінна быць не менш 0",
|
||||||
"out_of_range_error": "Павінна быць па-за дыяпазонам «{{start}}»-«{{end}}»",
|
"range_end_error": "Павінен перавышаць пачатак дыяпазону",
|
||||||
"lower_range_start_error": "Павінна быць менш за пачатак дыяпазону",
|
|
||||||
"greater_range_start_error": "Павінна быць больш за пачатак дыяпазону",
|
|
||||||
"greater_range_end_error": "Павінна быць больш за канец дыяпазону",
|
|
||||||
"subnet_error": "Адрасы павінны быць усярэдзіне адной падсеткі",
|
|
||||||
"gateway_or_subnet_invalid": "Некарэктная маска падсеткі",
|
|
||||||
"dhcp_form_gateway_input": "IP-адрас шлюза",
|
"dhcp_form_gateway_input": "IP-адрас шлюза",
|
||||||
"dhcp_form_subnet_input": "Маска падсеціва",
|
"dhcp_form_subnet_input": "Маска падсеціва",
|
||||||
"dhcp_form_range_title": "Дыяпазон IP-адрасоў",
|
"dhcp_form_range_title": "Дыяпазон IP-адрасоў",
|
||||||
@@ -511,7 +503,6 @@
|
|||||||
"statistics_clear_confirm": "Вы ўпэўнены, што хочаце ачысціць статыстыку?",
|
"statistics_clear_confirm": "Вы ўпэўнены, што хочаце ачысціць статыстыку?",
|
||||||
"statistics_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання статыстыкі? Пры скарачэнні інтэрвалу дадзеныя могуць быць згублены",
|
"statistics_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання статыстыкі? Пры скарачэнні інтэрвалу дадзеныя могуць быць згублены",
|
||||||
"statistics_cleared": "Статыстыка паспяхова вычышчана",
|
"statistics_cleared": "Статыстыка паспяхова вычышчана",
|
||||||
"statistics_enable": "Уключыць статыстыку",
|
|
||||||
"interval_hours": "{{count}} гадзіна",
|
"interval_hours": "{{count}} гадзіна",
|
||||||
"interval_hours_plural": "{{count}} гадзін",
|
"interval_hours_plural": "{{count}} гадзін",
|
||||||
"filters_configuration": "Налада фільтраў",
|
"filters_configuration": "Налада фільтраў",
|
||||||
@@ -621,8 +612,6 @@
|
|||||||
"click_to_view_queries": "Націсніце, каб прагледзець запыты",
|
"click_to_view_queries": "Націсніце, каб прагледзець запыты",
|
||||||
"port_53_faq_link": "Порт 53 часта заняты службамі \"DNSStubListener\" ці \"systemd-resolved\". Азнаёмцеся з <0>інструкцыяй</0> пра тое, як гэта дазволіць.",
|
"port_53_faq_link": "Порт 53 часта заняты службамі \"DNSStubListener\" ці \"systemd-resolved\". Азнаёмцеся з <0>інструкцыяй</0> пра тое, як гэта дазволіць.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home скіне ўсе DNS-запыты ад гэтага кліента.",
|
"adg_will_drop_dns_queries": "AdGuard Home скіне ўсе DNS-запыты ад гэтага кліента.",
|
||||||
"filter_allowlist": "УВАГА: Гэта дзеянне таксама выключыць правіла «{{disallowed_rule}}» са спіса дазволеных кліентаў.",
|
"client_not_in_allowed_clients": "Кліент не дазволены, бо яго няма ў спісе \"Дазволеных кліентаў\".",
|
||||||
"last_rule_in_allowlist": "Няможна заблакаваць гэтага кліента, бо вынятак правіла «{{disallowed_rule}}» АДКЛЮЧЫЦЬ рэжым белага спіса.",
|
"experimental": "Эксперыментальны"
|
||||||
"experimental": "Эксперыментальны",
|
|
||||||
"use_saved_key": "Скарыстаць захаваны раней ключ"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dhcp_leases": "DHCP раздадени адреси",
|
"dhcp_leases": "DHCP раздадени адреси",
|
||||||
"dhcp_leases_not_found": "Няма намерени активни DHCP адреси",
|
"dhcp_leases_not_found": "Няма намерени активни DHCP адреси",
|
||||||
"form_error_required": "Задължително поле",
|
"form_error_required": "Задължително поле",
|
||||||
"form_error_ip_format": "Невалиден IP адрес",
|
"form_error_ip_format": "Невалиден IPv4 адрес",
|
||||||
"form_error_positive": "Проверете дали е положително число",
|
"form_error_positive": "Проверете дали е положително число",
|
||||||
"dhcp_form_gateway_input": "IP шлюз",
|
"dhcp_form_gateway_input": "IP шлюз",
|
||||||
"dhcp_form_subnet_input": "Мрежова маска",
|
"dhcp_form_subnet_input": "Мрежова маска",
|
||||||
@@ -31,7 +31,6 @@
|
|||||||
"dashboard": "Табло",
|
"dashboard": "Табло",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"filters": "Филтри",
|
"filters": "Филтри",
|
||||||
"filter": "Филтър",
|
|
||||||
"query_log": "История на заявките",
|
"query_log": "История на заявките",
|
||||||
"faq": "ЧЗВ",
|
"faq": "ЧЗВ",
|
||||||
"version": "версия",
|
"version": "версия",
|
||||||
@@ -42,7 +41,6 @@
|
|||||||
"copyright": "Авторско право",
|
"copyright": "Авторско право",
|
||||||
"homepage": "Домашна страница",
|
"homepage": "Домашна страница",
|
||||||
"report_an_issue": "Съобщи за проблем",
|
"report_an_issue": "Съобщи за проблем",
|
||||||
"privacy_policy": "Правила за поверителност",
|
|
||||||
"enable_protection": "Разреши защита",
|
"enable_protection": "Разреши защита",
|
||||||
"enabled_protection": "Защитата е разрешена",
|
"enabled_protection": "Защитата е разрешена",
|
||||||
"disable_protection": "Забрани защита",
|
"disable_protection": "Забрани защита",
|
||||||
@@ -72,7 +70,6 @@
|
|||||||
"enforce_safe_search": "Включи Безопасно Търсене",
|
"enforce_safe_search": "Включи Безопасно Търсене",
|
||||||
"no_servers_specified": "Няма избрани услуги",
|
"no_servers_specified": "Няма избрани услуги",
|
||||||
"general_settings": "Общи настройки",
|
"general_settings": "Общи настройки",
|
||||||
"custom_filtering_rules": "Местни правила за филтриране",
|
|
||||||
"upstream_dns": "Главен DNS сървър",
|
"upstream_dns": "Главен DNS сървър",
|
||||||
"test_upstream_btn": "Тествай главния DNS",
|
"test_upstream_btn": "Тествай главния DNS",
|
||||||
"apply_btn": "Приложи",
|
"apply_btn": "Приложи",
|
||||||
@@ -89,7 +86,6 @@
|
|||||||
"rules_count_table_header": "Правила общо",
|
"rules_count_table_header": "Правила общо",
|
||||||
"last_time_updated_table_header": "Последно обновен",
|
"last_time_updated_table_header": "Последно обновен",
|
||||||
"actions_table_header": "Действия",
|
"actions_table_header": "Действия",
|
||||||
"edit_table_action": "Редактирай",
|
|
||||||
"delete_table_action": "Изтрий",
|
"delete_table_action": "Изтрий",
|
||||||
"filters_and_hosts_hint": "AdGuard Home разбира adblock и host синтаксис.",
|
"filters_and_hosts_hint": "AdGuard Home разбира adblock и host синтаксис.",
|
||||||
"cancel_btn": "Откажи",
|
"cancel_btn": "Откажи",
|
||||||
@@ -134,9 +130,6 @@
|
|||||||
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
||||||
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
||||||
"default": "По подразбиране",
|
|
||||||
"custom_ip": "Персонализиран IP",
|
|
||||||
"dns_over_quic": "DNS-over-QUIC",
|
|
||||||
"plain_dns": "Обикновен DNS",
|
"plain_dns": "Обикновен DNS",
|
||||||
"source_label": "Източник",
|
"source_label": "Източник",
|
||||||
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
||||||
@@ -224,26 +217,8 @@
|
|||||||
"form_error_password": "Паролата не съвпада",
|
"form_error_password": "Паролата не съвпада",
|
||||||
"reset_settings": "Изтрий всички настройки",
|
"reset_settings": "Изтрий всички настройки",
|
||||||
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
||||||
"settings_custom": "Персонализиране",
|
"disable_ipv6": "Изключете IPv6 протокола",
|
||||||
"table_client": "Клиент",
|
|
||||||
"table_name": "Име",
|
|
||||||
"save_btn": "Запази",
|
|
||||||
"name": "Име",
|
|
||||||
"clients_not_found": "Нямa намерени адреси",
|
|
||||||
"check_updates_now": "Провери за актуализации",
|
"check_updates_now": "Провери за актуализации",
|
||||||
"domain": "Домейн",
|
|
||||||
"disabled": "Деактивиран",
|
|
||||||
"username_label": "Потребител",
|
|
||||||
"username_placeholder": "Въведете потребител",
|
|
||||||
"password_label": "Парола",
|
|
||||||
"password_placeholder": "Въведете парола",
|
|
||||||
"network": "Мрежа",
|
|
||||||
"descr": "Описание",
|
|
||||||
"show_blocked_responses": "Блокирано",
|
"show_blocked_responses": "Блокирано",
|
||||||
"show_whitelisted_responses": "В белия списък",
|
|
||||||
"show_processed_responses": "Обработен",
|
|
||||||
"allowed": "В белия списък",
|
|
||||||
"filter_category_general": "General",
|
|
||||||
"filter_category_security": "Сигурност",
|
|
||||||
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Nastavení DHCP IPv4",
|
"dhcp_ipv4_settings": "Nastavení DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Nastavení DHCP IPv6",
|
"dhcp_ipv6_settings": "Nastavení DHCP IPv6",
|
||||||
"form_error_required": "Povinné pole",
|
"form_error_required": "Povinné pole",
|
||||||
"form_error_ip4_format": "Neplatná adresa IPv4",
|
"form_error_ip4_format": "Neplatný formát IPv4",
|
||||||
"form_error_ip4_range_start_format": "Neplatná adresa IPv4 na začátku rozsahu",
|
"form_error_ip6_format": "Neplatný formát IPv6",
|
||||||
"form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu",
|
"form_error_ip_format": "Neplatný formát IP",
|
||||||
"form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány",
|
"form_error_mac_format": "Neplatný formát MAC",
|
||||||
"form_error_ip6_format": "Neplatná adresa IPv6",
|
"form_error_client_id_format": "Neplatný formát ID klienta",
|
||||||
"form_error_ip_format": "Neplatná adresa IP",
|
|
||||||
"form_error_mac_format": "Neplatná adresa MAC",
|
|
||||||
"form_error_client_id_format": "Neplatné ID klienta",
|
|
||||||
"form_error_server_name": "Neplatný název serveru",
|
"form_error_server_name": "Neplatný název serveru",
|
||||||
"form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
"form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Musí být větší než začátek rozsahu",
|
||||||
"lower_range_start_error": "Musí být menší než začátek rozsahu",
|
|
||||||
"greater_range_start_error": "Musí být větší než začátek rozsahu",
|
|
||||||
"greater_range_end_error": "Musí být větší než konec rozsahu",
|
|
||||||
"subnet_error": "Adresy musí být v jedné podsíti",
|
|
||||||
"gateway_or_subnet_invalid": "Neplatná maska podsítě",
|
|
||||||
"dhcp_form_gateway_input": "IP brána",
|
"dhcp_form_gateway_input": "IP brána",
|
||||||
"dhcp_form_subnet_input": "Maska podsítě",
|
"dhcp_form_subnet_input": "Maska podsítě",
|
||||||
"dhcp_form_range_title": "Rozsah IP adres",
|
"dhcp_form_range_title": "Rozsah IP adres",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP IPv4-indstillinger",
|
"dhcp_ipv4_settings": "DHCP IPv4-indstillinger",
|
||||||
"dhcp_ipv6_settings": "DHCP IPv6-indstillinger",
|
"dhcp_ipv6_settings": "DHCP IPv6-indstillinger",
|
||||||
"form_error_required": "Obligatorisk felt",
|
"form_error_required": "Obligatorisk felt",
|
||||||
"form_error_ip4_format": "Ugyldig IPv4-adresse",
|
"form_error_ip4_format": "Ugyldigt IPv4-format",
|
||||||
"form_error_ip4_range_start_format": "Ugyldig IPv4-adresse for områdestart",
|
"form_error_ip6_format": "Ugyldigt IPv6-format",
|
||||||
"form_error_ip4_range_end_format": "Ugyldig IPv4-adresse for områdeafslutning",
|
"form_error_ip_format": "Ugyldigt IP-format",
|
||||||
"form_error_ip4_gateway_format": "Ugyldig IPv4-adresse for gateway",
|
"form_error_mac_format": "Ugyldigt MAC-format",
|
||||||
"form_error_ip6_format": "Ugyldig IPv6-adresse",
|
"form_error_client_id_format": "Ugyldigt klient-ID format",
|
||||||
"form_error_ip_format": "Ugyldig IP-adresse",
|
|
||||||
"form_error_mac_format": "Ugyldig MAC-adresse",
|
|
||||||
"form_error_client_id_format": "Ugyldigt klient-ID",
|
|
||||||
"form_error_server_name": "Ugyldigt servernavn",
|
"form_error_server_name": "Ugyldigt servernavn",
|
||||||
"form_error_subnet": "Subnet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"",
|
"form_error_subnet": "Subnet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Skal være større end starten på intervallet",
|
||||||
"lower_range_start_error": "Skal være mindre end starten på området",
|
|
||||||
"greater_range_start_error": "Skal være større end starten på området",
|
|
||||||
"greater_range_end_error": "Skal være større end slutningen på området",
|
|
||||||
"subnet_error": "Adresser ska være i ét undernet",
|
|
||||||
"gateway_or_subnet_invalid": "Undernetmaske ugyldig",
|
|
||||||
"dhcp_form_gateway_input": "Gateway IP",
|
"dhcp_form_gateway_input": "Gateway IP",
|
||||||
"dhcp_form_subnet_input": "Undernetmaske",
|
"dhcp_form_subnet_input": "Undernetmaske",
|
||||||
"dhcp_form_range_title": "Interval af IP-adresser",
|
"dhcp_form_range_title": "Interval af IP-adresser",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP-IPv4-Einstellungen",
|
"dhcp_ipv4_settings": "DHCP-IPv4-Einstellungen",
|
||||||
"dhcp_ipv6_settings": "DHCP-IPv6-Einstellungen",
|
"dhcp_ipv6_settings": "DHCP-IPv6-Einstellungen",
|
||||||
"form_error_required": "Pflichtfeld",
|
"form_error_required": "Pflichtfeld",
|
||||||
"form_error_ip4_format": "Ungültige IPv4-Adresse",
|
"form_error_ip4_format": "Ungültiges IPv4-Format",
|
||||||
"form_error_ip4_range_start_format": "Ungültiger Bereichsbeginn der IPv4-Adresse",
|
"form_error_ip6_format": "Ungültiges IPv6-Format",
|
||||||
"form_error_ip4_range_end_format": "Ungültiges Bereichsende der IPv4-Adresse",
|
"form_error_ip_format": "Ungültiges IPv4-Format",
|
||||||
"form_error_ip4_gateway_format": "Ungültiges Gateway-IPv4-Adresse",
|
"form_error_mac_format": "Ungültiges MAC-Format",
|
||||||
"form_error_ip6_format": "Ungültige IPv6-Adresse",
|
"form_error_client_id_format": "Ungültiges Client-ID-Format",
|
||||||
"form_error_ip_format": "Ungültige IP-Adresse",
|
|
||||||
"form_error_mac_format": "Ungültiges Format der MAC-Adresse",
|
|
||||||
"form_error_client_id_format": "Ungültiges Client-ID",
|
|
||||||
"form_error_server_name": "Ungültiger Servername",
|
"form_error_server_name": "Ungültiger Servername",
|
||||||
"form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
|
"form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
|
||||||
"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",
|
||||||
"out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen",
|
"range_end_error": "Muss größer als der Bereichsbeginn sein",
|
||||||
"lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein",
|
|
||||||
"greater_range_start_error": "Muss größer als der Bereichsbeginn sein",
|
|
||||||
"greater_range_end_error": "Muss größer als das Bereichsende sein",
|
|
||||||
"subnet_error": "Die Adressen müssen innerhalb eines Subnetzes liegen",
|
|
||||||
"gateway_or_subnet_invalid": "Ungültige Subnetzmaske",
|
|
||||||
"dhcp_form_gateway_input": "Gateway-IP",
|
"dhcp_form_gateway_input": "Gateway-IP",
|
||||||
"dhcp_form_subnet_input": "Subnetz-Maske",
|
"dhcp_form_subnet_input": "Subnetz-Maske",
|
||||||
"dhcp_form_range_title": "Bereich von IP-Adressen",
|
"dhcp_form_range_title": "Bereich von IP-Adressen",
|
||||||
@@ -314,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
|
"install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
|
||||||
"install_settings_all_interfaces": "Alle Schnittstellen",
|
"install_settings_all_interfaces": "Alle Schnittstellen",
|
||||||
"install_auth_title": "Authentifizierung",
|
"install_auth_title": "Authentifizierung",
|
||||||
"install_auth_desc": "Die Passwort-Authentifizierung für Ihre AdGuard Home Admin-Web-Oberfläche muss konfiguriert werden. Auch wenn AdGuard Home nur in Ihrem lokalen Netzwerk zugänglich ist, ist es dennoch wichtig, es vor unberechtigtem Zugriff zu schützen.",
|
"install_auth_desc": "Die Passwortauthentifizierung für Ihre AdGuard Home Administrator-Weboberfläche muss konfiguriert sein. Auch wenn AdGuard Home nur in Ihrem lokalen Netzwerk zugänglich ist, ist es dennoch wichtig, es vor unbefugtem Zugriff zu schützen.",
|
||||||
"install_auth_username": "Benutzername",
|
"install_auth_username": "Benutzername",
|
||||||
"install_auth_password": "Passwort",
|
"install_auth_password": "Passwort",
|
||||||
"install_auth_confirm": "Passwort bestätigen",
|
"install_auth_confirm": "Passwort bestätigen",
|
||||||
@@ -606,7 +598,7 @@
|
|||||||
"cache_ttl_min_override_desc": "Überschreibt den TTL-Minimalwert, der vom vorgeschalteten Server empfangen wurde. Dieser Wert darf nicht mehr als 3600 (Sek.) (≙ 1 Stunde) betragen.",
|
"cache_ttl_min_override_desc": "Überschreibt den TTL-Minimalwert, der vom vorgeschalteten Server empfangen wurde. Dieser Wert darf nicht mehr als 3600 (Sek.) (≙ 1 Stunde) betragen.",
|
||||||
"cache_ttl_max_override_desc": "Überschreibt den TLL-Maximalwert, der vom vorgeschalteten Server empfangen wurde.",
|
"cache_ttl_max_override_desc": "Überschreibt den TLL-Maximalwert, der vom vorgeschalteten Server empfangen wurde.",
|
||||||
"ttl_cache_validation": "Der minimale Cache des TTL-Wertes muss kleiner oder gleich dem maximalen Wert sein",
|
"ttl_cache_validation": "Der minimale Cache des TTL-Wertes muss kleiner oder gleich dem maximalen Wert sein",
|
||||||
"cache_optimistic": "Optimistisches Zwischenspeichern",
|
"cache_optimistic": "Optimistisches Caching",
|
||||||
"cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Zwischenspeicher antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.",
|
"cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Zwischenspeicher antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.",
|
||||||
"filter_category_general": "Allgemein",
|
"filter_category_general": "Allgemein",
|
||||||
"filter_category_security": "Sicherheit",
|
"filter_category_security": "Sicherheit",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP IPv4 Settings",
|
"dhcp_ipv4_settings": "DHCP IPv4 Settings",
|
||||||
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
|
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
|
||||||
"form_error_required": "Required field",
|
"form_error_required": "Required field",
|
||||||
"form_error_ip4_format": "Invalid IPv4 address",
|
"form_error_ip4_format": "Invalid IPv4 format",
|
||||||
"form_error_ip4_range_start_format": "Invalid IPv4 address of the range start",
|
"form_error_ip6_format": "Invalid IPv6 format",
|
||||||
"form_error_ip4_range_end_format": "Invalid IPv4 address of the range end",
|
"form_error_ip_format": "Invalid IP format",
|
||||||
"form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway",
|
"form_error_mac_format": "Invalid MAC format",
|
||||||
"form_error_ip6_format": "Invalid IPv6 address",
|
"form_error_client_id_format": "Invalid client ID format",
|
||||||
"form_error_ip_format": "Invalid IP address",
|
|
||||||
"form_error_mac_format": "Invalid MAC address",
|
|
||||||
"form_error_client_id_format": "Invalid client ID",
|
|
||||||
"form_error_server_name": "Invalid server name",
|
"form_error_server_name": "Invalid server name",
|
||||||
"form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
|
"form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Must be greater than range start",
|
||||||
"lower_range_start_error": "Must be lower than range start",
|
|
||||||
"greater_range_start_error": "Must be greater than range start",
|
|
||||||
"greater_range_end_error": "Must be greater than range end",
|
|
||||||
"subnet_error": "Addresses must be in one subnet",
|
|
||||||
"gateway_or_subnet_invalid": "Subnet mask invalid",
|
|
||||||
"dhcp_form_gateway_input": "Gateway IP",
|
"dhcp_form_gateway_input": "Gateway IP",
|
||||||
"dhcp_form_subnet_input": "Subnet mask",
|
"dhcp_form_subnet_input": "Subnet mask",
|
||||||
"dhcp_form_range_title": "Range of IP addresses",
|
"dhcp_form_range_title": "Range of IP addresses",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Configuración DHCP IPv4",
|
"dhcp_ipv4_settings": "Configuración DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Configuración DHCP IPv6",
|
"dhcp_ipv6_settings": "Configuración DHCP IPv6",
|
||||||
"form_error_required": "Campo obligatorio",
|
"form_error_required": "Campo obligatorio",
|
||||||
"form_error_ip4_format": "Dirección IPv4 no válida",
|
"form_error_ip4_format": "Formato IPv4 no válido",
|
||||||
"form_error_ip4_range_start_format": "Dirección IPv4 no válida del inicio de rango",
|
"form_error_ip6_format": "Formato IPv6 no válido",
|
||||||
"form_error_ip4_range_end_format": "Dirección IPv4 no válida del final de rango",
|
"form_error_ip_format": "Formato IP no válido",
|
||||||
"form_error_ip4_gateway_format": "Dirección IPv4 no válida de la puerta de enlace",
|
"form_error_mac_format": "Formato MAC no válido",
|
||||||
"form_error_ip6_format": "Dirección IPv6 no válida",
|
"form_error_client_id_format": "Formato de ID de cliente no válido",
|
||||||
"form_error_ip_format": "Dirección IP no válida",
|
|
||||||
"form_error_mac_format": "Dirección MAC no válida",
|
|
||||||
"form_error_client_id_format": "ID de cliente no válido",
|
|
||||||
"form_error_server_name": "Nombre de servidor no válido",
|
"form_error_server_name": "Nombre de servidor no válido",
|
||||||
"form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
|
"form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Debe ser mayor que el inicio de rango",
|
||||||
"lower_range_start_error": "Debe ser inferior que el inicio de rango",
|
|
||||||
"greater_range_start_error": "Debe ser mayor que el inicio de rango",
|
|
||||||
"greater_range_end_error": "Debe ser mayor que el final de rango",
|
|
||||||
"subnet_error": "Las direcciones deben estar en una subred",
|
|
||||||
"gateway_or_subnet_invalid": "Máscara de subred no válida",
|
|
||||||
"dhcp_form_gateway_input": "IP de puerta de enlace",
|
"dhcp_form_gateway_input": "IP de puerta de enlace",
|
||||||
"dhcp_form_subnet_input": "Máscara de subred",
|
"dhcp_form_subnet_input": "Máscara de subred",
|
||||||
"dhcp_form_range_title": "Rango de direcciones IP",
|
"dhcp_form_range_title": "Rango de direcciones IP",
|
||||||
|
|||||||
@@ -206,10 +206,8 @@
|
|||||||
"custom_ip": "آی پی دستی",
|
"custom_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",
|
|
||||||
"form_enter_rate_limit": "میزان محدودیت را وارد کنید",
|
"form_enter_rate_limit": "میزان محدودیت را وارد کنید",
|
||||||
"rate_limit": "میزان محدودیت",
|
"rate_limit": "میزان محدودیت",
|
||||||
"edns_enable": "فعالسازی زیرشبکه کلاینت EDNS",
|
"edns_enable": "فعالسازی زیرشبکه کلاینت EDNS",
|
||||||
@@ -401,7 +399,6 @@
|
|||||||
"encryption_key_source_content": "چسباندن محتوای کلید خصوصی",
|
"encryption_key_source_content": "چسباندن محتوای کلید خصوصی",
|
||||||
"stats_params": "پیکربندی آمار",
|
"stats_params": "پیکربندی آمار",
|
||||||
"config_successfully_saved": "پیکربندی با موفقیت ذخیره شد",
|
"config_successfully_saved": "پیکربندی با موفقیت ذخیره شد",
|
||||||
"interval_6_hour": "6 ساعت",
|
|
||||||
"interval_24_hour": "24 ساعت",
|
"interval_24_hour": "24 ساعت",
|
||||||
"interval_days": "{{value}} روز",
|
"interval_days": "{{value}} روز",
|
||||||
"interval_days_plural": "{{count}} روز",
|
"interval_days_plural": "{{count}} روز",
|
||||||
@@ -412,7 +409,7 @@
|
|||||||
"statistics_configuration": "پیکربندی آمارها",
|
"statistics_configuration": "پیکربندی آمارها",
|
||||||
"statistics_retention": "مدت حفظ آمارها",
|
"statistics_retention": "مدت حفظ آمارها",
|
||||||
"statistics_retention_desc": "اگر مقدار فاصله را کاهش دهید،برخی داده ها از بین خواهد رفت",
|
"statistics_retention_desc": "اگر مقدار فاصله را کاهش دهید،برخی داده ها از بین خواهد رفت",
|
||||||
"statistics_clear": "بازنشانی آمار",
|
"statistics_clear": " پاکسازی آمار",
|
||||||
"statistics_clear_confirm": "آیا واقعا میخواهید آمار را پاک کنید؟",
|
"statistics_clear_confirm": "آیا واقعا میخواهید آمار را پاک کنید؟",
|
||||||
"statistics_retention_confirm": "آیا واقعا میخواهید مدت حفظ آمار را تغییر دهید؟ اگر فاصله را کاهش دهید، برخی داده ها حذف میشود",
|
"statistics_retention_confirm": "آیا واقعا میخواهید مدت حفظ آمار را تغییر دهید؟ اگر فاصله را کاهش دهید، برخی داده ها حذف میشود",
|
||||||
"statistics_cleared": "آمارها با موفقیت حذف شد",
|
"statistics_cleared": "آمارها با موفقیت حذف شد",
|
||||||
@@ -442,6 +439,8 @@
|
|||||||
"domain_desc": "نامه دامنه یا علامت تطبیقی را برای بازنویسی وارد کنید.",
|
"domain_desc": "نامه دامنه یا علامت تطبیقی را برای بازنویسی وارد کنید.",
|
||||||
"example_rewrite_domain": "فقط بازنویسی پاسخ برای این دامنه.",
|
"example_rewrite_domain": "فقط بازنویسی پاسخ برای این دامنه.",
|
||||||
"example_rewrite_wildcard": "بازنویسی پاسخ ها برای همه زیردامنه های <0>example.org</0>.",
|
"example_rewrite_wildcard": "بازنویسی پاسخ ها برای همه زیردامنه های <0>example.org</0>.",
|
||||||
|
"disable_ipv6": "غیرفعالسازی IPv6",
|
||||||
|
"disable_ipv6_desc": "اگر این ویژگی فعال شده، همه جستارهای DNS برای آدرس های IPv6 (نوع AAAA) رها میشود.",
|
||||||
"fastest_addr": "سریعترین آدرس آی پی",
|
"fastest_addr": "سریعترین آدرس آی پی",
|
||||||
"autofix_warning_text": "اگر روی \"تعمیر\" کلیک کنید، AdGuardHome سیستم شما را برای استفاده از DNS سرور AdGuardHome پیکربندی می کند.",
|
"autofix_warning_text": "اگر روی \"تعمیر\" کلیک کنید، AdGuardHome سیستم شما را برای استفاده از DNS سرور AdGuardHome پیکربندی می کند.",
|
||||||
"autofix_warning_list": "این وظایف را اجرا میکند: <0>غیرفعالسازی DNSStubListener سیستم</0> <0>تنظیم آدرس DNS 127.0.0.1</0> سرور به <0>جایگزینی لینک نمادی هدف /etc/resolv.conf به/run/systemd/resolve/resolv.conf</0> <0>توقف DNSStubListener (بارگیری مجدد سرویس systemd-resolved)</0>",
|
"autofix_warning_list": "این وظایف را اجرا میکند: <0>غیرفعالسازی DNSStubListener سیستم</0> <0>تنظیم آدرس DNS 127.0.0.1</0> سرور به <0>جایگزینی لینک نمادی هدف /etc/resolv.conf به/run/systemd/resolve/resolv.conf</0> <0>توقف DNSStubListener (بارگیری مجدد سرویس systemd-resolved)</0>",
|
||||||
@@ -486,8 +485,5 @@
|
|||||||
"rewritten": "بازنویسی شده",
|
"rewritten": "بازنویسی شده",
|
||||||
"safe_search": "جستجوی اَمن",
|
"safe_search": "جستجوی اَمن",
|
||||||
"blocklist": "لیست سیاه",
|
"blocklist": "لیست سیاه",
|
||||||
"milliseconds_abbreviation": "هـ ثـ",
|
"milliseconds_abbreviation": "هـ ثـ"
|
||||||
"filter_category_general": "General",
|
|
||||||
"filter_category_security": "مسدودسازی بدافزار و فیشینگ",
|
|
||||||
"filter_category_other": "ساير"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,628 +0,0 @@
|
|||||||
{
|
|
||||||
"client_settings": "Päätelaiteasetukset",
|
|
||||||
"example_upstream_reserved": "voit määrittää DNS-ylävirran <0>tietyille verkkotunnuksille</0>",
|
|
||||||
"example_upstream_comment": "voit määrittää kommentin",
|
|
||||||
"upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirran palvelimia samanaikaisesti.",
|
|
||||||
"parallel_requests": "Rinnakkaiset pyynnöt",
|
|
||||||
"load_balancing": "Kuormantasaus",
|
|
||||||
"load_balancing_desc": "Lähetä pyyntö yhdelle ylävirran palvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.",
|
|
||||||
"bootstrap_dns": "Bootstrap DNS-palvelimet",
|
|
||||||
"bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.",
|
|
||||||
"local_ptr_title": "Yksityiset käänteiset DNS-palvelimet",
|
|
||||||
"local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien päätelaitteiden osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, käyttää AdGuard Home käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.",
|
|
||||||
"local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisiä DNS-resolvereita: {{ip}}.",
|
|
||||||
"local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteistä DNS-resolveria.",
|
|
||||||
"local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi",
|
|
||||||
"resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä",
|
|
||||||
"resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-kyselyt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, lähtevät palvelimet päätelaitteille, joilla on julkiset IP-osoitteet).",
|
|
||||||
"use_private_ptr_resolvers_title": "Käytä yksityisiä käänteisiä DNS-resolvereita",
|
|
||||||
"use_private_ptr_resolvers_desc": "Suorita käänteiset DNS-selvitykset paikallisesti tarjotuille osoitteille käyttäen näitä ylävirran palvelimia. Jos ei käytössä, vastaa AdGuard Home kaikkiin sen tyyppisiin PTR-pyyntöihin NXDOMAIN-arvolla, pois lukien DHCP, /etc/hosts, yms. -tiedoista tunnistettut päätelaitteet.",
|
|
||||||
"check_dhcp_servers": "Etsi DHCP-palvelimia",
|
|
||||||
"save_config": "Tallenna asetukset",
|
|
||||||
"enabled_dhcp": "DHCP-palvelin otettiin käyttöön",
|
|
||||||
"disabled_dhcp": "DHCP-palvelin poistettiin käytöstä",
|
|
||||||
"unavailable_dhcp": "DHCP ei ole käytettävissä",
|
|
||||||
"unavailable_dhcp_desc": "AdGuard Home ei voi suorittaa DHCP-palvelinta käyttöjärjestelmässäsi",
|
|
||||||
"dhcp_title": "DHCP-palvelin (kokeellinen!)",
|
|
||||||
"dhcp_description": "Jos reitittimessäsi ei ole DHCP-asetuksia, voit käyttää AdGuard Homen omaa sisäänrakennettua DHCP-palvelinta.",
|
|
||||||
"dhcp_enable": "Ota DHCP-palvelin käyttöön",
|
|
||||||
"dhcp_disable": "Poista DHCP-palvelin käytöstä",
|
|
||||||
"dhcp_not_found": "On turvallista ottaa sisäänrakennettu DHCP-palvelin käyttöön, koska AdGuard Home ei havainnut verkossa muita aktiivisia DHCP-palvelimia. Suosittelemme, että varmistat tämän vielä itse, koska automaattinen tunnistus ei ole 100% varma.",
|
|
||||||
"dhcp_found": "Verkossa havaittiin aktiivinen DHCP-palvelin. Sisäänrakennetun DHCP-palvelimen käyttöönotto ei ole turvallista.",
|
|
||||||
"dhcp_leases": "DHCP-lainat",
|
|
||||||
"dhcp_static_leases": "Kiinteät DHCP-lainat",
|
|
||||||
"dhcp_leases_not_found": "DHCP-lainoja ei löytynyt",
|
|
||||||
"dhcp_config_saved": "DHCP-asetukset tallennettiin",
|
|
||||||
"dhcp_ipv4_settings": "DHCP:n IPv4-asetukset",
|
|
||||||
"dhcp_ipv6_settings": "DHCP:n IPv6-asetukset",
|
|
||||||
"form_error_required": "Vaaditaan",
|
|
||||||
"form_error_ip4_format": "Virheellinen IPv4-osoite",
|
|
||||||
"form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite",
|
|
||||||
"form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite",
|
|
||||||
"form_error_ip4_gateway_format": "Virheellinen yhdyskäytävän IPv4-osoite",
|
|
||||||
"form_error_ip6_format": "Virheellinen IPv6-osoite",
|
|
||||||
"form_error_ip_format": "Virheellinen IP-osoite",
|
|
||||||
"form_error_mac_format": "Virheellinen MAC-osoite",
|
|
||||||
"form_error_client_id_format": "Virheellinen päätelaitteen ID",
|
|
||||||
"form_error_server_name": "Virheellinen palvelimen nimi",
|
|
||||||
"form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
|
|
||||||
"form_error_positive": "Oltava suurempi kuin 0",
|
|
||||||
"form_error_negative": "Oltava yhtä suuri tai suurempi kuin 0",
|
|
||||||
"out_of_range_error": "Oltava alueen \"{{start}}\"-\"{{end}}\" ulkopuolella",
|
|
||||||
"lower_range_start_error": "Oltava alueen aloitusarvoa pienempi",
|
|
||||||
"greater_range_start_error": "Oltava alueen aloitusarvoa suurempi",
|
|
||||||
"greater_range_end_error": "Oltava alueen päätösarvoa pienempi",
|
|
||||||
"subnet_error": "Osoitteiden tulee olla yhdessä aliverkossa",
|
|
||||||
"gateway_or_subnet_invalid": "Virheellinen aliverkon peite",
|
|
||||||
"dhcp_form_gateway_input": "Yhdyskäytävän IP-osoite",
|
|
||||||
"dhcp_form_subnet_input": "Aliverkon peite",
|
|
||||||
"dhcp_form_range_title": "IP-osoitealue",
|
|
||||||
"dhcp_form_range_start": "Alueen aloitus",
|
|
||||||
"dhcp_form_range_end": "Alueen päätös",
|
|
||||||
"dhcp_form_lease_title": "DHCP-lainan kesto (sekunteina)",
|
|
||||||
"dhcp_form_lease_input": "Lainan kesto",
|
|
||||||
"dhcp_interface_select": "Valitse DHCP:lle käytettävä verkkosovitin",
|
|
||||||
"dhcp_hardware_address": "Laiteosoite (MAC)",
|
|
||||||
"dhcp_ip_addresses": "IP-osoitteet",
|
|
||||||
"ip": "IP",
|
|
||||||
"dhcp_table_hostname": "Isäntänimi",
|
|
||||||
"dhcp_table_expires": "Erääntyy",
|
|
||||||
"dhcp_warning": "Jos tahdot kuitenkin ottaa DHCP-palvelimen käyttöön, varmista, ettei verkossasi ole muita aktiivisia DHCP-palvelimia, koska tämä voi rikkoa Internet-yhteyden muilta verkon laitteilta!",
|
|
||||||
"dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta.",
|
|
||||||
"dhcp_static_ip_error": "Jotta DHCP-palvelinta voidaan käyttää, on määritettävä kiinteä IP-osoite. AdGuard Home ei voinut tunnistaa, onko tälle verkkosovittimelle määritetty IP-osoite kiinteä. Määritä kiinteä IP-osoite itse.",
|
|
||||||
"dhcp_dynamic_ip_found": "Järjestelmäsi käyttää verkkosovittimelle <0>{{interfaceName}}</0> dynaamista IP-osoitetta. Jotta voit käyttää DHCP-palvelinta, on sovittimelle määritettävä kiinteä IP-osoite. Nykyinen IP-osoitteesi on <0>{{ipAddress}}</0>. Tämä osoite määritetään automaattisesti kiinteäksi, jos painat \"Ota DHCP-palvelin käyttöön\" -painiketta.",
|
|
||||||
"dhcp_lease_added": "Kiinteä laina \"{{key}}\" on lisätty",
|
|
||||||
"dhcp_lease_deleted": "Kiinteä laina \"{{key}}\" poistettiin",
|
|
||||||
"dhcp_new_static_lease": "Uusi kiinteä laina",
|
|
||||||
"dhcp_static_leases_not_found": "Kiinteitä DHCP-lainoja ei löytynyt",
|
|
||||||
"dhcp_add_static_lease": "Lisää kiinteä laina",
|
|
||||||
"dhcp_reset_leases": "Tyhjennä kaikki lainat",
|
|
||||||
"dhcp_reset_leases_confirm": "Haluatko varmasti tyhjentää kaikki lainat?",
|
|
||||||
"dhcp_reset_leases_success": "DHCP-lainat tyhjennettiin",
|
|
||||||
"dhcp_reset": "Haluatko varmasti palauttaa DHCP-asetukset?",
|
|
||||||
"country": "Maa",
|
|
||||||
"city": "Kaupunki",
|
|
||||||
"delete_confirm": "Haluatko varmasti poistaa kohteen \"{{key}}\"?",
|
|
||||||
"form_enter_hostname": "Syötä isäntänimi",
|
|
||||||
"error_details": "Virheen tiedot",
|
|
||||||
"response_details": "Vastauksen tiedot",
|
|
||||||
"request_details": "Pyynnön tiedot",
|
|
||||||
"client_details": "Päätelaitteen tiedot",
|
|
||||||
"details": "Tiedot",
|
|
||||||
"back": "Takaisin",
|
|
||||||
"dashboard": "Tila",
|
|
||||||
"settings": "Asetukset",
|
|
||||||
"filters": "Suodattimet",
|
|
||||||
"filter": "Suodatin",
|
|
||||||
"query_log": "Pyyntöhistoria",
|
|
||||||
"compact": "Tiivis",
|
|
||||||
"nothing_found": "Ei tuloksia",
|
|
||||||
"faq": "UKK",
|
|
||||||
"version": "Versio",
|
|
||||||
"address": "Osoite",
|
|
||||||
"protocol": "Protokolla",
|
|
||||||
"on": "Käytössä",
|
|
||||||
"off": "Ei käytössä",
|
|
||||||
"copyright": "Tekijänoikeus",
|
|
||||||
"homepage": "Kotisivu",
|
|
||||||
"report_an_issue": "Ilmoita ongelmasta",
|
|
||||||
"privacy_policy": "Tietosuojakäytäntö",
|
|
||||||
"enable_protection": "Ota suojaus käyttöön",
|
|
||||||
"enabled_protection": "Suojaus otettiin käyttöön",
|
|
||||||
"disable_protection": "Poista suojaus käytöstä",
|
|
||||||
"disabled_protection": "Suojaus poistettiin käytöstä",
|
|
||||||
"refresh_statics": "Päivitä tilastot",
|
|
||||||
"dns_query": "DNS-pyyntöä",
|
|
||||||
"blocked_by": "<0>Suodatinten estämää</0>",
|
|
||||||
"stats_malware_phishing": "Estetyt haittaohjelmat/tietojenkalastelut",
|
|
||||||
"stats_adult": "Estetyt aikuisille tarkoitetut sivustot",
|
|
||||||
"stats_query_domain": "Kysytyimmät verkkotunnukset",
|
|
||||||
"for_last_24_hours": "viimeisten 24 tunnin ajalta",
|
|
||||||
"for_last_days": "viimeisen {{count}} päivän ajalta",
|
|
||||||
"for_last_days_plural": "viimeisten {{count}} päivän ajalta",
|
|
||||||
"stats_disabled": "Tilastointi ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksista</0>.",
|
|
||||||
"stats_disabled_short": "Tilastointi ei ole käytössä",
|
|
||||||
"no_domains_found": "Verkkotunnuksia ei löytynyt",
|
|
||||||
"requests_count": "Pyyntöjen määrä",
|
|
||||||
"top_blocked_domains": "Estetyimmät verkkotunnukset",
|
|
||||||
"top_clients": "Käytetyimmät päätelaitteet",
|
|
||||||
"no_clients_found": "Päätelaitteita ei löytynyt",
|
|
||||||
"general_statistics": "Yleiset tilastot",
|
|
||||||
"number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisen {{count}} päivän ajalta",
|
|
||||||
"number_of_dns_query_days_plural": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta",
|
|
||||||
"number_of_dns_query_24_hours": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten 24 tunnin ajalta",
|
|
||||||
"number_of_dns_query_blocked_24_hours": "Mainoseston suodattimien ja hosts-estolistojen estämien DNS-pyyntöjen määrä",
|
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuardin Turvallinen selaus -moduulin estämien DNS-pyyntöjen määrä",
|
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Estettyjen aikuisille tarkoitettujen sivustojen määrä",
|
|
||||||
"enforced_save_search": "Turvallinen haku pakotettiin",
|
|
||||||
"number_of_dns_query_to_safe_search": "DNS-pyyntöjen määrä, joille turvallinen haku pakotettiin käyttöön",
|
|
||||||
"average_processing_time": "Keskimääräinen käsittelyaika",
|
|
||||||
"average_processing_time_hint": "Keskimääräinen DNS-pyynnön käsittelyyn kulutettu aika millisekunteina",
|
|
||||||
"block_domain_use_filters_and_hosts": "Estä verkkotunnuksia suodattimilla ja hosts-tiedostoilla",
|
|
||||||
"filters_block_toggle_hint": "Voit määrittää estosääntöjä <a>suodatinasetuksissa</a>.",
|
|
||||||
"use_adguard_browsing_sec": "Käytä AdGuardin turvallisen selauksen palvelua",
|
|
||||||
"use_adguard_browsing_sec_hint": "AdGuard Home tarkistaa onko verkkotunnus turvallisen selauksen verkkopalvelun estämä. Se käyttää tarkastukseen yksityisyyspainotteista rajapintaa: palvelimelle lähetetään vain pieni osa verkkotunnuksen SHA256-hajautusarvosta.",
|
|
||||||
"use_adguard_parental": "Käytä AdGuardin lapsilukko-palvelua",
|
|
||||||
"use_adguard_parental_hint": "AdGuard Home tarkistaa, sisältääkö verkkotunnus aikuisille tarkoitettua sisältöä. Se käyttää samaa yksityisyyspainotteista rajapintaa, kuin turvallisen selauksen palvelu.",
|
|
||||||
"enforce_safe_search": "Pakota turvallinen haku",
|
|
||||||
"enforce_save_search_hint": "AdGuard Home voi pakottaa turvallisen haun käyttöön seuraavissa hakukoneissa: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
|
||||||
"no_servers_specified": "Palvelimia ei ole määritetty",
|
|
||||||
"general_settings": "Yleiset asetukset",
|
|
||||||
"dns_settings": "DNS-asetukset",
|
|
||||||
"dns_blocklists": "DNS-estolistat",
|
|
||||||
"dns_allowlists": "DNS-sallittujen listat",
|
|
||||||
"dns_blocklists_desc": "AdGuard Home estää estolistalla olevat verkkotunnukset.",
|
|
||||||
"dns_allowlists_desc": "DNS-sallittujen listalla olevat verkkotunnukset sallitaan myös silloin, jos ne ovat jollain muulla estolistalla.",
|
|
||||||
"custom_filtering_rules": "Omat suodatussäännöt",
|
|
||||||
"encryption_settings": "Salausasetukset",
|
|
||||||
"dhcp_settings": "DHCP-asetukset",
|
|
||||||
"upstream_dns": "Ylävirran DNS-palvelimet",
|
|
||||||
"upstream_dns_help": "Syötä yksi palvelinosoite per rivi. <a>Lue lisää</a> ylävirran DNS-palvelinten määrityksestä.",
|
|
||||||
"upstream_dns_configured_in_file": "Määritetty tiedostossa {{path}}",
|
|
||||||
"test_upstream_btn": "Testaa ylävirtoja",
|
|
||||||
"upstreams": "Ylävirrat",
|
|
||||||
"apply_btn": "Käytä",
|
|
||||||
"disabled_filtering_toast": "Suodatus poistettiin käytöstä",
|
|
||||||
"enabled_filtering_toast": "Suodatus otettiin käyttöön",
|
|
||||||
"disabled_safe_browsing_toast": "Turvallinen selaus poistettiin käytöstä",
|
|
||||||
"enabled_safe_browsing_toast": "Turvallinen selaus otettiin käyttöön",
|
|
||||||
"disabled_parental_toast": "Lapsilukko poistettiin käytöstä",
|
|
||||||
"enabled_parental_toast": "Lapsilukko otettiin käyttöön",
|
|
||||||
"disabled_safe_search_toast": "Turvallinen haku poistettiin käytöstä",
|
|
||||||
"enabled_save_search_toast": "Turvallinen haku otettiin käyttöön",
|
|
||||||
"enabled_table_header": "Käytössä",
|
|
||||||
"name_table_header": "Nimi",
|
|
||||||
"list_url_table_header": "Listan URL",
|
|
||||||
"rules_count_table_header": "Sääntöjä",
|
|
||||||
"last_time_updated_table_header": "Viimeisin päivitys",
|
|
||||||
"actions_table_header": "Toiminnot",
|
|
||||||
"request_table_header": "Pyyntö",
|
|
||||||
"edit_table_action": "Muokkaa",
|
|
||||||
"delete_table_action": "Poista",
|
|
||||||
"elapsed": "Kesto",
|
|
||||||
"filters_and_hosts_hint": "AdGuard Home ymmärtää mainoseston perussääntöjen sekä hosts-tiedostojen syntakseja.",
|
|
||||||
"no_blocklist_added": "Estolistoja ei ole lisätty",
|
|
||||||
"no_whitelist_added": "Sallittujen listoja ei ole lisätty",
|
|
||||||
"add_blocklist": "Lisää estolista",
|
|
||||||
"add_allowlist": "Lisää sallittujen lista",
|
|
||||||
"cancel_btn": "Peruuta",
|
|
||||||
"enter_name_hint": "Syötä nimi",
|
|
||||||
"enter_url_or_path_hint": "Syötä listan URL-osoite tai tarkka tiedostosijainti",
|
|
||||||
"check_updates_btn": "Tarkista päivitykset",
|
|
||||||
"new_blocklist": "Uusi estolista",
|
|
||||||
"new_allowlist": "Uusi sallittujen lista",
|
|
||||||
"edit_blocklist": "Muokkaa estolistaa",
|
|
||||||
"edit_allowlist": "Muokkaa sallittujen listaa",
|
|
||||||
"choose_blocklist": "Valitse estolistat",
|
|
||||||
"choose_allowlist": "Valitse sallittujen listat",
|
|
||||||
"enter_valid_blocklist": "Syötä estolistan URL-osoite.",
|
|
||||||
"enter_valid_allowlist": "Syötä sallittujen listan URL-osoite.",
|
|
||||||
"form_error_url_format": "Virheellinen URL-osoitteen muoto",
|
|
||||||
"form_error_url_or_path_format": "Syötä listan URL-osoite tai tarkka tiedostosijainti",
|
|
||||||
"custom_filter_rules": "Omat suodatussäännöt",
|
|
||||||
"custom_filter_rules_hint": "Syötä yksi sääntö per rivi. Voit käyttää mainoseston sääntöjen tai hosts-tiedostojen syntakseja.",
|
|
||||||
"examples_title": "Esimerkkejä",
|
|
||||||
"example_meaning_filter_block": "estä pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin",
|
|
||||||
"example_meaning_filter_whitelist": "salli pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin",
|
|
||||||
"example_meaning_host_block": "AdGuard Home palauttaa verkkotunnukselle example.org osoitteen 127.0.0.1 (muttei sen aliverkkotunnuksille)",
|
|
||||||
"example_comment": "! Tähän tulee kommentti",
|
|
||||||
"example_comment_meaning": "voit määrittää kommentin",
|
|
||||||
"example_comment_hash": "# Myös tämä on kommentti",
|
|
||||||
"example_regex_meaning": "estä pääsy säännöllistä lauseketta vastaaviin verkkotunnuksiin",
|
|
||||||
"example_upstream_regular": "tavallinen DNS (UDP)",
|
|
||||||
"example_upstream_dot": "salattu <0>DNS-over-TLS</0>",
|
|
||||||
"example_upstream_doh": "salattu <0>DNS-over-HTTPS</0>",
|
|
||||||
"example_upstream_doq": "salattu <0>DNS-over-QUIC</0>",
|
|
||||||
"example_upstream_sdns": "voit käyttää <0>DNS Stamp</0> -merkintöjä <1>DNSCrypt</1> tai <2>DNS-over-HTTPS</2> -resolvereille",
|
|
||||||
"example_upstream_tcp": "tavallinen DNS (TCP)",
|
|
||||||
"all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
|
|
||||||
"updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
|
|
||||||
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
|
|
||||||
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": ei voitu käyttää, tarkista sen oikeinkirjoitus",
|
|
||||||
"unblock": "Salli",
|
|
||||||
"block": "Estä",
|
|
||||||
"disallow_this_client": "Estä tämä päätelaite",
|
|
||||||
"allow_this_client": "Salli tämä päätelaite",
|
|
||||||
"block_for_this_client_only": "Estä vain tältä päätelaitteelta",
|
|
||||||
"unblock_for_this_client_only": "Salli vain tälle päätelaitteelle",
|
|
||||||
"time_table_header": "Aika",
|
|
||||||
"date": "Päiväys",
|
|
||||||
"domain_name_table_header": "Verkkotunnus",
|
|
||||||
"domain_or_client": "Verkkotunnus tai päätelaite",
|
|
||||||
"type_table_header": "Tyyppi",
|
|
||||||
"response_table_header": "Vastaus",
|
|
||||||
"response_code": "Vastauksen koodi",
|
|
||||||
"client_table_header": "Asiakas",
|
|
||||||
"empty_response_status": "Tyhjä",
|
|
||||||
"show_all_filter_type": "Näytä kaikki",
|
|
||||||
"show_filtered_type": "Näytä suodatetut",
|
|
||||||
"no_logs_found": "Historiatietoja ei ole",
|
|
||||||
"refresh_btn": "Päivitä",
|
|
||||||
"previous_btn": "Edellinen",
|
|
||||||
"next_btn": "Seuraava",
|
|
||||||
"loading_table_status": "Ladataan...",
|
|
||||||
"page_table_footer_text": "Sivu",
|
|
||||||
"rows_table_footer_text": "riviä",
|
|
||||||
"updated_custom_filtering_toast": "Omat suodatussäännöt päivitettiin",
|
|
||||||
"rule_removed_from_custom_filtering_toast": "Sääntö poistettiin omista suodatussäännöistä: {{rule}}",
|
|
||||||
"rule_added_to_custom_filtering_toast": "Sääntö lisättiin omiin suodatussääntöihin: {{rule}}",
|
|
||||||
"query_log_response_status": "Tila: {{value}}",
|
|
||||||
"query_log_filtered": "Suodattanut {{filter}}",
|
|
||||||
"query_log_confirm_clear": "Haluatko varmasti tyhjentää pyyntöhistorian?",
|
|
||||||
"query_log_cleared": "Pyyntöhistoria tyhjennettiin",
|
|
||||||
"query_log_updated": "Pyyntöhistoria päivitettiin",
|
|
||||||
"query_log_clear": "Tyhjennä pyyntöhistoria",
|
|
||||||
"query_log_retention": "Pyyntöhistorian säilytys",
|
|
||||||
"query_log_enable": "Käytä historiaa",
|
|
||||||
"query_log_configuration": "Historian määritys",
|
|
||||||
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>",
|
|
||||||
"query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
|
|
||||||
"query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistoriasi säilytysaikaa? Jos lyhennät aikaa, joitakin tietoja menetetään",
|
|
||||||
"anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
|
|
||||||
"anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin",
|
|
||||||
"dns_config": "DNS-palvelimen määritys",
|
|
||||||
"dns_cache_config": "DNS-välimuistin määritys",
|
|
||||||
"dns_cache_config_desc": "Täällä voit määrittää DNS-välimuistin asetukset",
|
|
||||||
"blocking_mode": "Estotila",
|
|
||||||
"default": "Oletus",
|
|
||||||
"nxdomain": "NXDOMAIN",
|
|
||||||
"refused": "REFUSED",
|
|
||||||
"null_ip": "Tyhjä IP",
|
|
||||||
"custom_ip": "Oma IP",
|
|
||||||
"blocking_ipv4": "IPv4-esto",
|
|
||||||
"blocking_ipv6": "IPv6-esto",
|
|
||||||
"dnscrypt": "DNSCrypt",
|
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
|
||||||
"dns_over_quic": "DNS-over-QUIC",
|
|
||||||
"client_id": "Päätelaitteen ID",
|
|
||||||
"client_id_placeholder": "Syötä päätelaitteen ID",
|
|
||||||
"client_id_desc": "Eri päätelaitteet voidaan tunnistaa erityisillä tunnisteilla. <a>Täältä</a> löydät lisätietoja päätelaitteiden tunnistuksesta.",
|
|
||||||
"download_mobileconfig_doh": "Lataa .mobileconfig-tiedosto DNS-over-HTTPS -käytölle",
|
|
||||||
"download_mobileconfig_dot": "Lataa .mobileconfig-tiedosto DNS-over-TLS -käytölle",
|
|
||||||
"download_mobileconfig": "Lataa asetustiedosto",
|
|
||||||
"plain_dns": "Tavallinen DNS",
|
|
||||||
"form_enter_rate_limit": "Syötä rajoitus",
|
|
||||||
"rate_limit": "Pyyntöjen ajoitus",
|
|
||||||
"edns_enable": "Käytä EDNS-päätelaitealivekkoa",
|
|
||||||
"edns_cs_desc": "Lähetä päätelaitteiden aliverkot DNS-palvelimille.",
|
|
||||||
"rate_limit_desc": "Päätelaitteelle sallittu pyyntöjen enimmäismäärä sekunnissa. Arvo 0 tarkoittaa rajatonta.",
|
|
||||||
"blocking_ipv4_desc": "Estettyyn A-pyyntöön palautettava IP-osoite",
|
|
||||||
"blocking_ipv6_desc": "Estettyyn AAAA-pyyntöön palautettava IP-osoite",
|
|
||||||
"blocking_mode_default": "Oletus: Vastaa IP-nollaosoitteella (0.0.0.0 korvaa A; :: korvaa AAAA) kun estetään mainoseston säännöllä; vastaa säännön määrittämällä IP-osoitteella kun estetään /etc/hosts-tyyppisellä säännöllä",
|
|
||||||
"blocking_mode_refused": "REFUSED: Vastaa REFUSED-koodilla",
|
|
||||||
"blocking_mode_nxdomain": "NXDOMAIN: Vastaa NXDOMAIN-koodilla",
|
|
||||||
"blocking_mode_null_ip": "Tyhjä IP: Vastaa IP-nollaosoitteella (0.0.0.0 korvaa A; :: korvaa AAAA)",
|
|
||||||
"blocking_mode_custom_ip": "Oma IP: Vastaa itse määritetyllä IP-osoitteella",
|
|
||||||
"upstream_dns_client_desc": "Jos tämä on tyhjä, käyttää AdGuard Home <0>DNS-asetuksissa</0> määritettyjä palvelimia.",
|
|
||||||
"tracker_source": "Seurannan lähde",
|
|
||||||
"source_label": "Lähde",
|
|
||||||
"found_in_known_domain_db": "Löytyi tunnettujen verkkotunnusten tietokannasta.",
|
|
||||||
"category_label": "Luokitus",
|
|
||||||
"rule_label": "Säännöt",
|
|
||||||
"list_label": "Lista",
|
|
||||||
"unknown_filter": "Tuntematon suodatin {{filterId}}",
|
|
||||||
"known_tracker": "Tunnettu seuranta",
|
|
||||||
"install_welcome_title": "Tervetuloa AdGuard Homeen!",
|
|
||||||
"install_welcome_desc": "AdGuard Home on verkonlaajuinen mainoksia ja seurantoja estävä DNS-palvelin. Sen tarkoitus on mahdollistaa verkon sekä siihen liitettyjen laitteiden hallinta ja valvonta, eikä se vaadi asiakasohjelmistojen asennusta päätelaitteille.",
|
|
||||||
"install_settings_title": "Hallintapaneeli",
|
|
||||||
"install_settings_listen": "Käytettävä verkkosovitin",
|
|
||||||
"install_settings_port": "Portti",
|
|
||||||
"install_settings_interface_link": "AdGuard Home -asennuksesi hallintapaneeli on käytettävissä seuraavilla osoitteilla:",
|
|
||||||
"form_error_port": "Syötä oikea portin numero",
|
|
||||||
"install_settings_dns": "DNS-palvelin",
|
|
||||||
"install_settings_dns_desc": "Sinun on määritettävä laitteesi tai reitittimesi käyttämään DNS-palvelinta seuraavissa osoitteissa:",
|
|
||||||
"install_settings_all_interfaces": "Kaikki verkkosovittimet",
|
|
||||||
"install_auth_title": "Tunnistautuminen",
|
|
||||||
"install_auth_desc": "AdGuard Homen hallinnalle on määritettävä salasanasuojaus. Vaikka se olisikin tavoitettavissa vain lähiverkon välityksellä, on silti tärkeää suojata se luvattomalta käytöltä.",
|
|
||||||
"install_auth_username": "Käyttäjätunnus",
|
|
||||||
"install_auth_password": "Salasana",
|
|
||||||
"install_auth_confirm": "Vahvista salasana",
|
|
||||||
"install_auth_username_enter": "Syötä käyttäjätunnus",
|
|
||||||
"install_auth_password_enter": "Syötä salasana",
|
|
||||||
"install_step": "Vaihe",
|
|
||||||
"install_devices_title": "Määritä laitteet",
|
|
||||||
"install_devices_desc": "AdGuard Homen käytön aloittamiseksi, on laitteet määritettävä käyttämään sitä.",
|
|
||||||
"install_submit_title": "Onnittelut!",
|
|
||||||
"install_submit_desc": "Asennus on valmis ja AdGuard Home on valmis käyttöön.",
|
|
||||||
"install_devices_router": "Reititin",
|
|
||||||
"install_devices_router_desc": "Asennus kattaa kaikki reitittimeen liitetyt laitteet, eikä niitä tarvitse määrittää erikseen yksitellen.",
|
|
||||||
"install_devices_address": "AdGuard Homen DNS-palvelin kuuntelee seuraavissa osoitteissa",
|
|
||||||
"install_devices_router_list_1": "Avaa reitittimesi hallinta. Yleensä se avautuu selaimen kautta, URL-osoitteella, kuten http://192.168.0.1 tai http://192.168.1.1. Saatat joutua syöttämään käyttäjätunnuksen ja salasanan. Jos et muista tai tiedä sitä, voit yleensä palauttaa salasanan (ja kaikki muut!) reitittimen asetukset oletusarvoihin painamalla laitteessa olevaa reset-painiketta muutaman sekunnin ajan. Jos reitittimen määritys vaatii erillisen sovelluksen käyttöä, asenna se mobiililaitteelle tai tietokoneelle ja käytä reitittimen hallintaa sen kautta. Tutustu reitittimen käyttöoppaaseen.",
|
|
||||||
"install_devices_router_list_2": "Etsi DHCP/DNS-asetukset. Etsi kirjainyhdistelmää DNS sellaisen kenttien vierestä, joihin voidaan syöttää kaksi tai kolme numerosarjaa, joista jokainen on eroteltu neljään ryhmään, joista jokainen sisältää yhdestä kolmeen numeroa.",
|
|
||||||
"install_devices_router_list_3": "Syötä sinne AdGuard Home -palvelimesi osoitteet.",
|
|
||||||
"install_devices_router_list_4": "Joissakin reitittimissä ei ole mahdollista määrittää omaa DNS-palvelinta. Tällöin AdGuard Homen määritys <0>DHCP-palvelimeksi</0> voi auttaa. Muutoin on selvitettävä reitittimen käyttöohjeesta, miten sen DNS-palvelinasetukset muutetaan.",
|
|
||||||
"install_devices_windows_list_1": "Avaa \"Ohjauspaneeli\" Käynnistä-valikon tai Windowsin haun kautta.",
|
|
||||||
"install_devices_windows_list_2": "Avaa \"Verkko ja Internet\" -ryhmä ja sitten \"Verkko ja jakamiskeskus\".",
|
|
||||||
"install_devices_windows_list_3": "Etsi ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\" ja klikkaa sitä.",
|
|
||||||
"install_devices_windows_list_4": "Valitse aktiivinen yhteytesi, klikkaa sitä hiiren kakkospainikkeella ja valitse valikosta \"Ominaisuudet\".",
|
|
||||||
"install_devices_windows_list_5": "Etsi listasta \"Internet protokolla versio 4 (TCP/IP)\", valitse se ja klikkaa jälleen \"Ominaisuudet\".",
|
|
||||||
"install_devices_windows_list_6": "Valitse \"Käytä seuraavia DNS-palvelinten osoitteita\" ja syötä AdGuard Home -palvelimesi osoitteet.",
|
|
||||||
"install_devices_macos_list_1": "Avaa Omenavalikko ja valitse \"Järjestelmäasetukset \".",
|
|
||||||
"install_devices_macos_list_2": "Klikkaa \"Verkko\".",
|
|
||||||
"install_devices_macos_list_3": "Valitse listan ensimmäinen yhteys ja klikkaa \"Lisävalinnat\".",
|
|
||||||
"install_devices_macos_list_4": "Valitse DNS-välilehti ja syötä AdGuard Home -palvelimesi osoitteet.",
|
|
||||||
"install_devices_android_list_1": "Napauta \"Asetukset\" -kuvaketta Android-laitteesi aloitusnäytöstä tai sovellusvalikosta.",
|
|
||||||
"install_devices_android_list_2": "Napauta \"Yhteydet\" ja sitten \"Wi-Fi\". Näytetään kaikki käytettävissä olevat langattomat verkot (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
|
|
||||||
"install_devices_android_list_3": "Napauta yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".",
|
|
||||||
"install_devices_android_list_4": "Saatat joutua napauttamaan \"Lisäasetukset\" nähdäksesi lisää valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.",
|
|
||||||
"install_devices_android_list_5": "Syötä \"DNS 1\" ja \"DNS 2\" -kenttiin AdGuard Home -palvelimesi osoitteet.",
|
|
||||||
"install_devices_ios_list_1": "Valitse aloitusnäytöstä \"Asetukset\".",
|
|
||||||
"install_devices_ios_list_2": "Valitse vasemmalta \"Wi-Fi\" (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
|
|
||||||
"install_devices_ios_list_3": "Valitse yhdistetty verkko.",
|
|
||||||
"install_devices_ios_list_4": "Syötä \"DNS\" -kenttään AdGuard Home -palvelimesi osoitteet.",
|
|
||||||
"get_started": "Aloita",
|
|
||||||
"next": "Seuraava",
|
|
||||||
"open_dashboard": "Avaa hallintapaneeli",
|
|
||||||
"install_saved": "Tallennettiin",
|
|
||||||
"encryption_title": "Salaus",
|
|
||||||
"encryption_desc": "Salaus (HTTPS/TLS) DNS-palvelimelle ja selaimen hallintasivulle",
|
|
||||||
"encryption_config_saved": "Salausasetukset tallennettiin",
|
|
||||||
"encryption_server": "Palvelimen nimi",
|
|
||||||
"encryption_server_enter": "Syötä verkkotunnuksesi",
|
|
||||||
"encryption_server_desc": "HTTPS-yhteyden käyttöä varten, on syötettävä SSL- tai jokerivarmennetta vastaava palvelimen nimi. Jos kenttä on tyhjä, sallitaan kaikkien verkkotunnusten TLS-yhteydet.",
|
|
||||||
"encryption_redirect": "Automaattinen HTTPS-ohjaus",
|
|
||||||
"encryption_redirect_desc": "Jos käytössä, AdGuard Home ohjaa HTTP-osoitteet automaattisesti HTTPS-osoitteisiin.",
|
|
||||||
"encryption_https": "HTTPS-portti",
|
|
||||||
"encryption_https_desc": "Jos HTTPS-portti on määritetty, on AdGuard Homen hallintapaneeli käytettävissä HTTPS-yhteydellä ja lisäksi tämä mahdollistaa myös DNS-over-HTTPS -yhteyden '/dns-query' -kohteessa.",
|
|
||||||
"encryption_dot": "DNS-over-TLS -portti",
|
|
||||||
"encryption_dot_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-TLS -palvelimen tässä portissa.",
|
|
||||||
"encryption_doq": "DNS-over-QUIC -portti",
|
|
||||||
"encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC -palvelimen tässä portissa. Ominaisuus on kokeellinen, eikä välttämättä luotettava. Lisäksi tätä tukevia päätelaitteita ei vielä ole kovin paljon.",
|
|
||||||
"encryption_certificates": "Varmenteet",
|
|
||||||
"encryption_certificates_desc": "Salauksen käyttämiseksi, on syötettävä verkkotunnuksellesi myönnetty, aito SSL-varmenneketju. Voit hankkia ilmaisen varmenteen osoitteesta <0>{{link}}</0> tai ostaa sellaisen joltakin luotetulta varmentajalta.",
|
|
||||||
"encryption_certificates_input": "Kopioi/liitä PEM-koodatut varmenteesi tähän.",
|
|
||||||
"encryption_status": "Tila",
|
|
||||||
"encryption_expire": "Erääntyy",
|
|
||||||
"encryption_key": "Yksityinen avain",
|
|
||||||
"encryption_key_input": "Kopioi/liitä tähän varmenteesi PEM-koodattu yksityinen avain.",
|
|
||||||
"encryption_enable": "Käytä salausta (HTTPS, DNS-over-HTTPS ja DNS-over-TLS)",
|
|
||||||
"encryption_enable_desc": "Jos salaus on käytössä, AdGuard Homen hallinta on käytettävissä HTTPS-yhteydellä ja DNS-palvelin kuuntelee pyyntöjä DNS-over-HTTPS ja DNS-over-TLS -yhteyksillä.",
|
|
||||||
"encryption_chain_valid": "Varmenneketju on pätevä",
|
|
||||||
"encryption_chain_invalid": "Varmenneketju ei kelpaa",
|
|
||||||
"encryption_key_valid": "Yksityinen {{type}}-avain on pätevä",
|
|
||||||
"encryption_key_invalid": "Yksityinen {{type}}-avain ei kelpaa",
|
|
||||||
"encryption_subject": "Aihe",
|
|
||||||
"encryption_issuer": "Toimittaja",
|
|
||||||
"encryption_hostnames": "Isäntänimet",
|
|
||||||
"encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
|
|
||||||
"topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
|
|
||||||
"topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
|
|
||||||
"form_error_port_range": "Syötä portti väliltä 80-65535",
|
|
||||||
"form_error_port_unsafe": "Tämä portti ei ole turvallinen",
|
|
||||||
"form_error_equal": "Ei voi olla sama",
|
|
||||||
"form_error_password": "Salasanat eivät täsmää",
|
|
||||||
"reset_settings": "Tyhjennä asetukset",
|
|
||||||
"update_announcement": "AdGuard Home {{version}} on nyt saatavilla! <0>Klikkaa tästä</0> saadaksesi lisätietoja.",
|
|
||||||
"setup_guide": "Asennusopas",
|
|
||||||
"dns_addresses": "DNS-osoitteet",
|
|
||||||
"dns_start": "DNS-palvelin käynnistyy",
|
|
||||||
"dns_status_error": "Virhe tarkistettaessa DNS-palvelimen tilaa",
|
|
||||||
"down": "yhteydetön",
|
|
||||||
"fix": "Korjaa",
|
|
||||||
"dns_providers": "Katso <0>luettelo tunnetuista DNS-palveluista</0>, joista valita.",
|
|
||||||
"update_now": "Päivitä nyt",
|
|
||||||
"update_failed": "Automaattinen päivitys epäonnistui. Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
|
|
||||||
"processing_update": "Odota kun AdGuard Home päivittyy",
|
|
||||||
"clients_title": "Päätelaitteet",
|
|
||||||
"clients_desc": "Määritä AdGuard Homeen yhdistetyt päätelaitteet",
|
|
||||||
"settings_global": "Yleinen",
|
|
||||||
"settings_custom": "Oma",
|
|
||||||
"table_client": "Päätelaite",
|
|
||||||
"table_name": "Nimi",
|
|
||||||
"save_btn": "Tallenna",
|
|
||||||
"client_add": "Lisää päätelaite",
|
|
||||||
"client_new": "Uusi päätelaite",
|
|
||||||
"client_edit": "Muokkaa päätelaitetta",
|
|
||||||
"client_identifier": "Tunniste",
|
|
||||||
"ip_address": "IP-osoite",
|
|
||||||
"client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP-osoitteista, CIDR-merkinnöistä, MAC-osoitteista tai erityisistä päätelaitteen ID-tunnisteista (voidaan käyttää DoT/DoH/DoQ kanssa). <0>Täältä</0> voit lukea lisää päätelaitteiden tunnistuksesta.",
|
|
||||||
"form_enter_ip": "Syötä IP-osoite",
|
|
||||||
"form_enter_subnet_ip": "Syötä IP-osoite aliverkkoon \"{{cidr}}\"",
|
|
||||||
"form_enter_mac": "Syötä MAC-osoite",
|
|
||||||
"form_enter_id": "Muokkaa tunnistetta",
|
|
||||||
"form_add_id": "Lisää tunniste",
|
|
||||||
"form_client_name": "Syötä päätelaitteen nimi",
|
|
||||||
"name": "Nimi",
|
|
||||||
"client_global_settings": "Käytä yleisiä asetuksia",
|
|
||||||
"client_deleted": "Päätelaite \"{{key}}\" poistettiin",
|
|
||||||
"client_added": "Päätelaite \"{{key}}\" lisättiin",
|
|
||||||
"client_updated": "Päätelaite \"{{key}}\" päivitettiin",
|
|
||||||
"clients_not_found": "Päätelaitteita ei löytynyt",
|
|
||||||
"client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?",
|
|
||||||
"list_confirm_delete": "Haluatko varmasti poistaa tämän listan?",
|
|
||||||
"auto_clients_title": "Päätelaitteet (määrittämättömät)",
|
|
||||||
"auto_clients_desc": "Tiedot niistä AdGuard Homea käyttävistä päätelaitteista, joita ei ole määritetty asetuksissa",
|
|
||||||
"access_title": "Käytön asetukset",
|
|
||||||
"access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
|
|
||||||
"access_allowed_title": "Sallitut päätelaitteet",
|
|
||||||
"access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaitteiden ID-tunnisteista. Jos määritetty, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
|
|
||||||
"access_disallowed_title": "Kielletyt päätelaitteet",
|
|
||||||
"access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaitteiden ID-tunnisteista. Jos määritetty, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
|
|
||||||
"access_blocked_title": "Kielletyt verkkotunnukset",
|
|
||||||
"access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
|
|
||||||
"access_settings_saved": "Käytön asetukset tallennettiin",
|
|
||||||
"updates_checked": "Päivitykset tarkastettiin",
|
|
||||||
"updates_version_equal": "AdGuard Home on ajan tasalla",
|
|
||||||
"check_updates_now": "Tarkista päivitykset nyt",
|
|
||||||
"dns_privacy": "DNS-yksityisyys",
|
|
||||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Käytä merkkijonoa <1>{{address}}</1>.",
|
|
||||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Käytä merkkijonoa <1>{{address}}</1>.",
|
|
||||||
"setup_dns_privacy_3": "<0>Tässä on lista ohjelmistoista, joita voit käyttää.</0>",
|
|
||||||
"setup_dns_privacy_4": "iOS 14 ja macOS Big Sur -laitteille voidaan ladata erityinen '.mobileconfig' -tiedosto, joka lisää DNS-asetuksiin <highlight>DNS-over-HTTPS</highlight> tai <highlight>DNS-over-TLS</highlight> -palvelimet.",
|
|
||||||
"setup_dns_privacy_android_1": "Android 9 tukee DNS-over-TLS -toteutusta natiivisti. Se määritetään syöttämällä oma verkkotunnus kohtaan \"Asetukset\" → \"Yhteydet\" → \"Lisää yhteysasetuksia\" → \"Yksityinen DNS\".",
|
|
||||||
"setup_dns_privacy_android_2": "<0>AdGuard Androidille</0> tukee <1>DNS-over-HTTPS</1> ja <1>DNS-over-TLS</1> -toteutuksia.",
|
|
||||||
"setup_dns_privacy_android_3": "<0>Intra</0> lisää <1>DNS-over-HTTPS</1> tuen Androidiin.",
|
|
||||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> tukee <1>DNS-over-HTTPS</1>, mutta oman palvelimen käyttö' varten sille on luotava <2>DNS Stamp</2> -merkintä.",
|
|
||||||
"setup_dns_privacy_ios_2": "<0>AdGuard iOS:lle</0> tukee <1>DNS-over-HTTPS</1> ja <1>DNS-over-TLS</1> -toteutuksia.",
|
|
||||||
"setup_dns_privacy_other_title": "Muita toteutuksia",
|
|
||||||
"setup_dns_privacy_other_1": "AdGuard Home voi itse olla turvallinen DNS-päätelaite millä tahansa alustalla.",
|
|
||||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> tukee kaikkia tunnettuja turvallisia DNS-protokollia.",
|
|
||||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> tukee <1>DNS-over-HTTPS</1> -protokollaa.",
|
|
||||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> tukee <1>DNS-over-HTTPS</1> -toteutusta.",
|
|
||||||
"setup_dns_privacy_other_5": "Löydät lisää toteutuksia <0>täältä</0> ja <1>täältä</1>.",
|
|
||||||
"setup_dns_privacy_ioc_mac": "iOS ja macOS -asetukset",
|
|
||||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> tai <1>DNS-over-TLS</1> -toteutuksia varten, on AdGuard Homen <0>Salausasetukset</0> määritettävä.",
|
|
||||||
"rewrite_added": "Kohteen \"{{key}}\" DNS-uudelleenohjaus lisättiin",
|
|
||||||
"rewrite_deleted": "Kohteen \"{{key}}\" DNS-uudelleenohjaus poistettiin",
|
|
||||||
"rewrite_add": "Lisää DNS-uudelleenohjaus",
|
|
||||||
"rewrite_not_found": "DNS-uudelleenohjauksia ei löytynyt",
|
|
||||||
"rewrite_confirm_delete": "Haluatko varmasti poistaa DNS-uudelleenohjauksen kohteelle \"{{key}}\"?",
|
|
||||||
"rewrite_desc": "Mahdollistaa oman DNS-vastauksen helpon määrityksen tietylle verkkotunnukselle.",
|
|
||||||
"rewrite_applied": "Uudelleenohjattu säännöllä",
|
|
||||||
"rewrite_hosts_applied": "Hosts-tiedoston säännön korvaama",
|
|
||||||
"dns_rewrites": "DNS-uudelleenohjaukset",
|
|
||||||
"form_domain": "Syötä verkkotunnus tai jokerimerkki",
|
|
||||||
"form_answer": "Syötä IP-osoite tai verkkotunnus",
|
|
||||||
"form_error_domain_format": "Virheellinen verkkotunnuksen muoto",
|
|
||||||
"form_error_answer_format": "Virheellinen vastauksen muoto",
|
|
||||||
"configure": "Määritä",
|
|
||||||
"main_settings": "Pääasetukset",
|
|
||||||
"block_services": "Estä tietyt palvelut",
|
|
||||||
"blocked_services": "Estetyt palvelut",
|
|
||||||
"blocked_services_desc": "Mahdollistaa suosittujen sivustojen ja palveluiden nopean eston.",
|
|
||||||
"blocked_services_saved": "Estetyt palvelut tallennettiin",
|
|
||||||
"blocked_services_global": "Käytä yleisiä estettyjä palveluita",
|
|
||||||
"blocked_service": "Estetty palvelu",
|
|
||||||
"block_all": "Estä kaikki",
|
|
||||||
"unblock_all": "Salli kaikki",
|
|
||||||
"encryption_certificate_path": "Varmenteen sijainti",
|
|
||||||
"encryption_private_key_path": "Yksityisen avaimen sijainti",
|
|
||||||
"encryption_certificates_source_path": "Määritä varmennetiedoston sijainti",
|
|
||||||
"encryption_certificates_source_content": "Liitä varmenteen sisältö",
|
|
||||||
"encryption_key_source_path": "Määritä yksityisen avaimen tiedosto",
|
|
||||||
"encryption_key_source_content": "Liitä yksityisen avaimen sisältö",
|
|
||||||
"stats_params": "Tilastoinnin määritys",
|
|
||||||
"config_successfully_saved": "Asetukset tallennettiin",
|
|
||||||
"interval_6_hour": "6 tuntia",
|
|
||||||
"interval_24_hour": "24 tuntia",
|
|
||||||
"interval_days": "{{count}} päivä",
|
|
||||||
"interval_days_plural": "{{count}} päivää",
|
|
||||||
"domain": "Verkkotunnus",
|
|
||||||
"punycode": "Punycode",
|
|
||||||
"answer": "Vastaus",
|
|
||||||
"filter_added_successfully": "Lista lisättiin",
|
|
||||||
"filter_removed_successfully": "Lista poistettiin",
|
|
||||||
"filter_updated": "Listan päivitettiin",
|
|
||||||
"statistics_configuration": "Tilastoinnin määritys",
|
|
||||||
"statistics_retention": "Tilastojen säilytys",
|
|
||||||
"statistics_retention_desc": "Jos aikaa lyhennetään, joitakin tietoja menetetään.",
|
|
||||||
"statistics_clear": "Tyhjennä tilastot",
|
|
||||||
"statistics_clear_confirm": "Haluatko varmasti tyhjentää tilastot?",
|
|
||||||
"statistics_retention_confirm": "Haluatko varmasti muuttaa tilastojen säilytysaikaa? Jos aikaa lyhennetään, joitakin tietoja menetetään.",
|
|
||||||
"statistics_cleared": "Tilastot tyhjennettiin",
|
|
||||||
"statistics_enable": "Ota tilastointi käyttöön",
|
|
||||||
"interval_hours": "{{count}} tunti",
|
|
||||||
"interval_hours_plural": "{{count}} tuntia",
|
|
||||||
"filters_configuration": "Suodatinten määritys",
|
|
||||||
"filters_enable": "Ota suodattimet käyttöön",
|
|
||||||
"filters_interval": "Suodatinpäivitysten tiheys",
|
|
||||||
"disabled": "Ei käytössä",
|
|
||||||
"username_label": "Käyttäjätunnus",
|
|
||||||
"username_placeholder": "Syötä käyttäjätunnus",
|
|
||||||
"password_label": "Salasana",
|
|
||||||
"password_placeholder": "Syötä salasana",
|
|
||||||
"sign_in": "Kirjaudu",
|
|
||||||
"sign_out": "Kirjaudu ulos",
|
|
||||||
"forgot_password": "Salasana unohtunut?",
|
|
||||||
"forgot_password_desc": "Luo käyttäjätilillesi uusi salasana seuraamalla <0>näitä ohjeita</0>.",
|
|
||||||
"location": "Sijainti",
|
|
||||||
"orgname": "Organisaation nimi",
|
|
||||||
"netname": "Verkon nimi",
|
|
||||||
"network": "Verkko",
|
|
||||||
"descr": "Kuvaus",
|
|
||||||
"whois": "Whois",
|
|
||||||
"filtering_rules_learn_more": "<0>Lue lisää</0> omien hosts-listojesi luonnista.",
|
|
||||||
"blocked_by_response": "Vastauksen sisältämän CNAME:n tai IP:n estämä",
|
|
||||||
"blocked_by_cname_or_ip": "CNAME:n tai IP:n estämä",
|
|
||||||
"try_again": "Yritä uudelleen",
|
|
||||||
"domain_desc": "Syötä korvattava verkkotunnus tai jokerimerkki.",
|
|
||||||
"example_rewrite_domain": "korvaa vain tämän verkkotunnuksen vastaukset",
|
|
||||||
"example_rewrite_wildcard": "korvaa verkkotunnuksen <0>example.org</0> kaikkien aliverkkotunnusten vastaukset",
|
|
||||||
"rewrite_ip_address": "IP-osoite: käytä tätä IP-osoitetta A tai AAAA -vastauksessa.",
|
|
||||||
"rewrite_domain_name": "Verkkotunnus: lisää CNAME-tietue.",
|
|
||||||
"rewrite_A": "<0>A</0>: erityinen arvo, säilytä ylävirran <0>A</0>-tiedot",
|
|
||||||
"rewrite_AAAA": "<0>AAAA</0>: erityinen arvo, säilytä ylävirran <0>AAAA</0>-tiedot",
|
|
||||||
"disable_ipv6": "Älä selvitä IPv6-osoitteita",
|
|
||||||
"disable_ipv6_desc": "Hylkää kaikki IPv6-osoitteiden DNS-pyynnöt (tyyppi AAAA).",
|
|
||||||
"fastest_addr": "Nopein IP-osoite",
|
|
||||||
"fastest_addr_desc": "Lähetä pyynnöt kaikille DNS-palvelimille ja valitse vastauksista nopein IP-osoite. Tämä parantaa yleistä liitettävyyttä, joskin hidastaa DNS-pyyntöjä, koska AdGuard Homen on odotettava kaikkien DNS-palvelinten vastauksia.",
|
|
||||||
"autofix_warning_text": "Jos valitset \"Korjaa\", AdGuard Home määrittää järjestelmäsi käyttämään AdGuard Homen DNS-palvelinta.",
|
|
||||||
"autofix_warning_list": "Suorittaa toiminnot: <0>Poistaa käytöstä järjestelmän DNSStubListener-palvelun</0> <0>Määrittää DNS-palvelimen osoitteeksi 127.0.0.1</0> <0>Muuttaa sijainnnin /etc/resolv.conf symbolisen linkin kohteeksi /run/systemd/resolve/resolv.conf</0> <0>Pysäyttää DNSStubListener-palvelun (uudelleenlataa systemd-resolved -palvelu)</0>",
|
|
||||||
"autofix_warning_result": "Tämän jälkeen järjestelmäsi kaikki DNS-pyynnöt käsittelee oletusarvoisesti AdGuard Home.",
|
|
||||||
"tags_title": "Tunnisteet",
|
|
||||||
"tags_desc": "Voit valita päätelaitteeseen viittaavia tunnisteita. Tunnisteet voidaan sisällyttää suodatussääntöihin ja näin voit kohdistaa niitä tarkemmin. <0>Lue lisää</0>",
|
|
||||||
"form_select_tags": "Valitse päätelaitteen tunnisteet",
|
|
||||||
"check_title": "Tarkasta suodatus",
|
|
||||||
"check_desc": "Tarkasta suodatetaanko isäntänimeä",
|
|
||||||
"check": "Tarkasta",
|
|
||||||
"form_enter_host": "Syötä isäntänimi",
|
|
||||||
"filtered_custom_rules": "Suodatettu omilla suodatussäännöillä",
|
|
||||||
"choose_from_list": "Valitse listalta",
|
|
||||||
"add_custom_list": "Lisää oma lista",
|
|
||||||
"host_whitelisted": "Isäntä on sallittu",
|
|
||||||
"check_ip": "IP-osoitteet: {{ip}}",
|
|
||||||
"check_cname": "CNAME: {{cname}}",
|
|
||||||
"check_reason": "Syy: {{reason}}",
|
|
||||||
"check_service": "Palvelun nimi: {{service}}",
|
|
||||||
"service_name": "Palvelun nimi",
|
|
||||||
"check_not_found": "Ei löytynyt suodatinlistoilta",
|
|
||||||
"client_confirm_block": "Haluatko varmasti estää päätelaitteen \"{{ip}}\"?",
|
|
||||||
"client_confirm_unblock": "Haluatko varmasti sallia päätelaitteen \"{{ip}}\"?",
|
|
||||||
"client_blocked": "Päätelaite \"{{ip}}\" estettiin",
|
|
||||||
"client_unblocked": "Päätelaite \"{{ip}}\" sallittiin",
|
|
||||||
"static_ip": "Kiiteä IP-osoite",
|
|
||||||
"static_ip_desc": "AdGuard Home on palvelin, joten se tarvitsee kiinteän IP-osoitteen toimiakseen oikein. Muutoin reitittimesi saattaa määrittää sille jossakin vaiheessa uuden, dynaamisen IP-osoitteen.",
|
|
||||||
"set_static_ip": "Määritä kiinteä IP-osoite",
|
|
||||||
"install_static_ok": "Hyviä uutisia! Kiinteä IP-osoite on jo määritetty.",
|
|
||||||
"install_static_error": "AdGuard Home ei voi määrittää sitä tälle verkkosovittimelle automaattisesti. Etsi ohjeita tämän suorittamiseksi itse.",
|
|
||||||
"install_static_configure": "AdGuard Home havaitsi, että käytössä on dynaaminen IP-osoitteen <0>{{ip}}</0>. Haluatko määrittää sen kiinteäksi osoitteeksi?",
|
|
||||||
"confirm_static_ip": "AdGuard Home määrittää IP-osoitteen {{ip}} kiinteäksi. Haluatko jatkaa?",
|
|
||||||
"list_updated": "{{count}} lista päivitettiin",
|
|
||||||
"list_updated_plural": "{{count}} listaa päivitettiin",
|
|
||||||
"dnssec_enable": "Ota DNSSEC käyttöön",
|
|
||||||
"dnssec_enable_desc": "Määritä DNSSEC-lippu ulos lähteville DNS-pyynnöille ja tarkasta tulos (vaatii DNSSEC-yhteensopivan resolverin).",
|
|
||||||
"validated_with_dnssec": "DNSSEC-vahvistettu",
|
|
||||||
"all_queries": "Kaikki pyynnöt",
|
|
||||||
"show_blocked_responses": "Estetty",
|
|
||||||
"show_whitelisted_responses": "Sallittu",
|
|
||||||
"show_processed_responses": "Käsitelty",
|
|
||||||
"blocked_safebrowsing": "Turvallisen selauksen estämät",
|
|
||||||
"blocked_adult_websites": "Estetyt aikuisille tarkoitetut sivustot",
|
|
||||||
"blocked_threats": "Estetyt uhat",
|
|
||||||
"allowed": "Sallitut",
|
|
||||||
"filtered": "Suodatetut",
|
|
||||||
"rewritten": "Uudelleenohjatut",
|
|
||||||
"safe_search": "Turvallinen haku",
|
|
||||||
"blocklist": "Estolista",
|
|
||||||
"milliseconds_abbreviation": "ms",
|
|
||||||
"cache_size": "Välimuistin koko",
|
|
||||||
"cache_size_desc": "DNS-välimuistin koko (tavuina)",
|
|
||||||
"cache_ttl_min_override": "Korvaa vähimmäis-TTL",
|
|
||||||
"cache_ttl_max_override": "Korvaa enimmäis-TTL",
|
|
||||||
"enter_cache_size": "Syötä välimuistin koko (tavuina)",
|
|
||||||
"enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)",
|
|
||||||
"enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)",
|
|
||||||
"cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä time-to-live -arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.",
|
|
||||||
"cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden suurin time-to-live -arvo (sekunteina)",
|
|
||||||
"ttl_cache_validation": "Välimuistin TTL-vähimmäisarvon tulee olla pienempi tai sama kuin enimmäisarvon",
|
|
||||||
"cache_optimistic": "Optimistinen välimuisti",
|
|
||||||
"cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka sen tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
|
|
||||||
"filter_category_general": "Yleiset",
|
|
||||||
"filter_category_security": "Turvallisuus",
|
|
||||||
"filter_category_regional": "Alueelliset",
|
|
||||||
"filter_category_other": "Muut",
|
|
||||||
"filter_category_general_desc": "Listat, jotka estävät seurannan ja mainokset useimmilla laitteilla",
|
|
||||||
"filter_category_security_desc": "Tietojenkalastelu-, huijaus- ja muiden haitallisten verkkotunnusten estoon erikoistuneet listat",
|
|
||||||
"filter_category_regional_desc": "Listat, jotka painottavat alueellisia mainoksia ja seurantapalvelimia",
|
|
||||||
"filter_category_other_desc": "Muut estolistat",
|
|
||||||
"setup_config_to_enable_dhcp_server": "Määritä asetukset DHCP-palvelimen käyttöönottoa varten",
|
|
||||||
"original_response": "Alkuperäinen vastaus",
|
|
||||||
"click_to_view_queries": "Näytä pyynnöt",
|
|
||||||
"port_53_faq_link": "Portti 53 on usein \"DNSStubListener\" tai \"systemd-resolved\" -palveluiden varaama. Lue <0>nämä ohjeet</0> tämän ratkaisemiseksi.",
|
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home hylkää tämän päätelaitteen DNS-pyynnöt.",
|
|
||||||
"filter_allowlist": "VAROITUS: Toiminto ohittaa \"{{disallowed_rule}}\" -säännön sallittujen päätelaitteiden listalta.",
|
|
||||||
"last_rule_in_allowlist": "Et voi estää tätä päätelaitetta, koska säännön \"{{disallowed_rule}}\" ohitus POISTAA KÄYTÖSTÄ \"Sallitut päätelaitteet\" -listan.",
|
|
||||||
"experimental": "Kokeellinen",
|
|
||||||
"use_saved_key": "Käytä aiemmin tallennettua avainta"
|
|
||||||
}
|
|
||||||
@@ -36,11 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Paramètres IPv4 du DHCP",
|
"dhcp_ipv4_settings": "Paramètres IPv4 du DHCP",
|
||||||
"dhcp_ipv6_settings": "Paramètres IPv6 du DHCP",
|
"dhcp_ipv6_settings": "Paramètres IPv6 du DHCP",
|
||||||
"form_error_required": "Champ requis",
|
"form_error_required": "Champ requis",
|
||||||
"form_error_ip_format": "Adresse e-mail non valide",
|
"form_error_ip4_format": "Format IPv4 invalide",
|
||||||
|
"form_error_ip6_format": "Format IPv6 invalide",
|
||||||
|
"form_error_ip_format": "Format IPv4 invalide",
|
||||||
|
"form_error_mac_format": "Format MAC invalide",
|
||||||
|
"form_error_client_id_format": "Format d'ID client non valide",
|
||||||
"form_error_server_name": "Nom de serveur invalide",
|
"form_error_server_name": "Nom de serveur invalide",
|
||||||
"form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
|
"form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
|
||||||
"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",
|
||||||
"dhcp_form_gateway_input": "IP de la passerelle",
|
"dhcp_form_gateway_input": "IP de la passerelle",
|
||||||
"dhcp_form_subnet_input": "Masque de sous-réseau",
|
"dhcp_form_subnet_input": "Masque de sous-réseau",
|
||||||
"dhcp_form_range_title": "Rangée des adresses IP",
|
"dhcp_form_range_title": "Rangée des adresses IP",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
|
"form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
|
||||||
"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",
|
||||||
"dhcp_form_gateway_input": "Gateway IP",
|
"dhcp_form_gateway_input": "Gateway IP",
|
||||||
"dhcp_form_subnet_input": "Subnet maskiranje",
|
"dhcp_form_subnet_input": "Subnet maskiranje",
|
||||||
"dhcp_form_range_title": "Raspon IP adresa",
|
"dhcp_form_range_title": "Raspon IP adresa",
|
||||||
@@ -207,7 +208,7 @@
|
|||||||
"example_upstream_sdns": "možete koristiti <0>DNS Stamps</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> rezolvere",
|
"example_upstream_sdns": "možete koristiti <0>DNS Stamps</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> rezolvere",
|
||||||
"example_upstream_tcp": "zadani DNS (putem TCP)",
|
"example_upstream_tcp": "zadani DNS (putem TCP)",
|
||||||
"all_lists_up_to_date_toast": "Svi popisi su ažurirani",
|
"all_lists_up_to_date_toast": "Svi popisi su ažurirani",
|
||||||
"updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni",
|
"updated_upstream_dns_toast": "Ažurirani su upstream DNS poslužitelji",
|
||||||
"dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
|
"dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
|
||||||
"dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
|
"dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
|
||||||
"unblock": "Odblokiraj",
|
"unblock": "Odblokiraj",
|
||||||
@@ -234,7 +235,7 @@
|
|||||||
"loading_table_status": "Učitavanje...",
|
"loading_table_status": "Učitavanje...",
|
||||||
"page_table_footer_text": "Stranica",
|
"page_table_footer_text": "Stranica",
|
||||||
"rows_table_footer_text": "redova",
|
"rows_table_footer_text": "redova",
|
||||||
"updated_custom_filtering_toast": "Prilagođena pravila uspješno su spremljena",
|
"updated_custom_filtering_toast": "Ažurirana su prilagođena pravila filtriranja",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
@@ -305,7 +306,7 @@
|
|||||||
"install_settings_dns_desc": "Potrebno je postaviti uređaj ili router da koristi DNS poslužitelj na sljedećim adresama:",
|
"install_settings_dns_desc": "Potrebno je postaviti uređaj ili router da koristi DNS poslužitelj na sljedećim adresama:",
|
||||||
"install_settings_all_interfaces": "Sva sučelja",
|
"install_settings_all_interfaces": "Sva sučelja",
|
||||||
"install_auth_title": "Autentikacija",
|
"install_auth_title": "Autentikacija",
|
||||||
"install_auth_desc": "Provjera autentičnosti lozinke na web-sučelje AdGuard Home admin mora biti konfigurirana. Čak i ako je AdGuard Home dostupan samo u vašoj lokalnoj mreži, i dalje je važno zaštititi ga od neograničenog pristupa.",
|
"install_auth_desc": "Izrazito se preporučuje postavljanje autentikacije za web administratorsko sučelje AdGuard Home. Iako je dostupna samo u vašoj lokalnoj mreži, važno je zaštititi je od ne dozvoljenog pristupa.",
|
||||||
"install_auth_username": "Korisničko ime",
|
"install_auth_username": "Korisničko ime",
|
||||||
"install_auth_password": "Lozinka",
|
"install_auth_password": "Lozinka",
|
||||||
"install_auth_confirm": "Potvrdi lozinku",
|
"install_auth_confirm": "Potvrdi lozinku",
|
||||||
@@ -502,7 +503,6 @@
|
|||||||
"statistics_clear_confirm": "Jeste li sigurni da želite poništiti statistiku?",
|
"statistics_clear_confirm": "Jeste li sigurni da želite poništiti statistiku?",
|
||||||
"statistics_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje statistike? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
"statistics_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje statistike? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
||||||
"statistics_cleared": "Statistika je uspješno uklonjenja",
|
"statistics_cleared": "Statistika je uspješno uklonjenja",
|
||||||
"statistics_enable": "Omogući statistiku",
|
|
||||||
"interval_hours": "{{count}} sata/i",
|
"interval_hours": "{{count}} sata/i",
|
||||||
"interval_hours_plural": "{{count}} sata/i",
|
"interval_hours_plural": "{{count}} sata/i",
|
||||||
"filters_configuration": "Postavke filtara",
|
"filters_configuration": "Postavke filtara",
|
||||||
@@ -612,8 +612,6 @@
|
|||||||
"click_to_view_queries": "Kliknite za pregled upita",
|
"click_to_view_queries": "Kliknite za pregled upita",
|
||||||
"port_53_faq_link": "Port 53 često zauzimaju usluge \"DNSStubListener\" ili \"systemd-resolved\". Molimo pročitajte <0>ove upute</0> o tome kako to riješiti.",
|
"port_53_faq_link": "Port 53 često zauzimaju usluge \"DNSStubListener\" ili \"systemd-resolved\". Molimo pročitajte <0>ove upute</0> o tome kako to riješiti.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home odbaciti će sve DNS upite od ovog klijenta.",
|
"adg_will_drop_dns_queries": "AdGuard Home odbaciti će sve DNS upite od ovog klijenta.",
|
||||||
"filter_allowlist": "UPOZORENJE: Ova akcija će također isključiti pravilo \"{{disallowed_rule}}\" s popisa dopuštenih klijenata.",
|
"client_not_in_allowed_clients": "Klijent nije dopušten jer nije na popisu \"Dopuštenih klijenata\".",
|
||||||
"last_rule_in_allowlist": "Ovaj klijent nije moguće onemogućiti jer će isključivanje pravila \"{{disallowed_rule}}\" ONEMOGUĆITI popis \"Dopušteni klijenti\".",
|
"experimental": "Eksperimentalno"
|
||||||
"experimental": "Eksperimentalno",
|
|
||||||
"use_saved_key": "Korištenje prethodno spremljenog ključa"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
|
"form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
|
||||||
"form_error_positive": "0-nál nagyobbnak kell lennie",
|
"form_error_positive": "0-nál nagyobbnak kell lennie",
|
||||||
"form_error_negative": "Legalább 0-nak kell lennie",
|
"form_error_negative": "Legalább 0-nak kell lennie",
|
||||||
|
"range_end_error": "Nagyobbnak kell lennie, mint a tartomány kezdete",
|
||||||
"dhcp_form_gateway_input": "Átjáró IP",
|
"dhcp_form_gateway_input": "Átjáró IP",
|
||||||
"dhcp_form_subnet_input": "Alhálózati maszk",
|
"dhcp_form_subnet_input": "Alhálózati maszk",
|
||||||
"dhcp_form_range_title": "IP-címek tartománya",
|
"dhcp_form_range_title": "IP-címek tartománya",
|
||||||
@@ -612,6 +613,7 @@
|
|||||||
"click_to_view_queries": "Kattintson a lekérésekért",
|
"click_to_view_queries": "Kattintson a lekérésekért",
|
||||||
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához.",
|
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához.",
|
||||||
"adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.",
|
"adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.",
|
||||||
|
"client_not_in_allowed_clients": "Ez a kliens nincs engedélyezve, mivel nincs rajta az \"Engedélyezett kliensek\" listáján.",
|
||||||
"experimental": "Kísérleti",
|
"experimental": "Kísérleti",
|
||||||
"use_saved_key": "Előzőleg mentett kulcs használata"
|
"use_saved_key": "Előzőleg mentett kulcs használata"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
|
"form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
|
||||||
"form_error_positive": "Harus lebih dari 0",
|
"form_error_positive": "Harus lebih dari 0",
|
||||||
"form_error_negative": "Harus berjumlah 0 atau lebih besar dari 0",
|
"form_error_negative": "Harus berjumlah 0 atau lebih besar dari 0",
|
||||||
|
"range_end_error": "Harus lebih besar dari rentang awal",
|
||||||
"dhcp_form_gateway_input": "IP gateway",
|
"dhcp_form_gateway_input": "IP gateway",
|
||||||
"dhcp_form_subnet_input": "Subnet mask",
|
"dhcp_form_subnet_input": "Subnet mask",
|
||||||
"dhcp_form_range_title": "Rentang alamat IP",
|
"dhcp_form_range_title": "Rentang alamat IP",
|
||||||
@@ -611,6 +612,7 @@
|
|||||||
"click_to_view_queries": "Klik untuk lihat permintaan",
|
"click_to_view_queries": "Klik untuk lihat permintaan",
|
||||||
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini.",
|
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.",
|
"adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.",
|
||||||
|
"client_not_in_allowed_clients": "Klien tidak diizinkan karena tidak ada dalam daftar \"Klien yang diizinkan\".",
|
||||||
"experimental": "Eksperimental",
|
"experimental": "Eksperimental",
|
||||||
"use_saved_key": "Gunakan kunci yang disimpan sebelumnya"
|
"use_saved_key": "Gunakan kunci yang disimpan sebelumnya"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Impostazioni DHCP IPv4",
|
"dhcp_ipv4_settings": "Impostazioni DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Impostazioni DHCP IPv6",
|
"dhcp_ipv6_settings": "Impostazioni DHCP IPv6",
|
||||||
"form_error_required": "Campo richiesto",
|
"form_error_required": "Campo richiesto",
|
||||||
"form_error_ip4_format": "Indirizzo IPv4 non valido",
|
"form_error_ip4_format": "Formato IPv4 non valido",
|
||||||
"form_error_ip4_range_start_format": "Indirizzo IPV4 non valido dell'intervallo iniziale",
|
"form_error_ip6_format": "Formato IPv6 non valido",
|
||||||
"form_error_ip4_range_end_format": "Indirizzo IPV4 non valido dell'intervallo finale",
|
"form_error_ip_format": "Formato IPv4 non valido",
|
||||||
"form_error_ip4_gateway_format": "Indirizzo gateway IPv4 non valido",
|
"form_error_mac_format": "Formato MAC non valido",
|
||||||
"form_error_ip6_format": "Indirizzo IPv6 non valido",
|
"form_error_client_id_format": "Formato ID cliente non valido",
|
||||||
"form_error_ip_format": "Indirizzo IP non valido",
|
|
||||||
"form_error_mac_format": "Indirizzo MAC non valido",
|
|
||||||
"form_error_client_id_format": "ID cliente non valido",
|
|
||||||
"form_error_server_name": "Nome server non valido",
|
"form_error_server_name": "Nome server non valido",
|
||||||
"form_error_subnet": "La subnet \"{{cidr}}\" non contiene l\\'indirizzo IP \"{{ip}}\"",
|
"form_error_subnet": "La subnet \"{{cidr}}\" non contiene l\\'indirizzo IP \"{{ip}}\"",
|
||||||
"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)",
|
||||||
"out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Deve essere maggiore dell'intervallo di inizio",
|
||||||
"lower_range_start_error": "Deve essere inferiore dell\\'intervallo di inizio",
|
|
||||||
"greater_range_start_error": "Deve essere maggiore dell\\'intervallo di inizio",
|
|
||||||
"greater_range_end_error": "Deve essere maggiore dell\\'intervallo di fine",
|
|
||||||
"subnet_error": "Gli indirizzi devono trovarsi in una sottorete",
|
|
||||||
"gateway_or_subnet_invalid": "Maschera di sottorete non valida",
|
|
||||||
"dhcp_form_gateway_input": "IP Gateway",
|
"dhcp_form_gateway_input": "IP Gateway",
|
||||||
"dhcp_form_subnet_input": "Maschera di sottorete",
|
"dhcp_form_subnet_input": "Maschera di sottorete",
|
||||||
"dhcp_form_range_title": "Intervallo di indirizzi IP",
|
"dhcp_form_range_title": "Intervallo di indirizzi IP",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "IPアドレス「{{ip}}」はサブネット「{{cidr}}」に含まれていません",
|
"form_error_subnet": "IPアドレス「{{ip}}」はサブネット「{{cidr}}」に含まれていません",
|
||||||
"form_error_positive": "0より大きい必要があります",
|
"form_error_positive": "0より大きい必要があります",
|
||||||
"form_error_negative": "0以上である必要があります",
|
"form_error_negative": "0以上である必要があります",
|
||||||
|
"range_end_error": "範囲開始よりも大きくなければなりません",
|
||||||
"dhcp_form_gateway_input": "ゲートウェイIP",
|
"dhcp_form_gateway_input": "ゲートウェイIP",
|
||||||
"dhcp_form_subnet_input": "サブネットマスク",
|
"dhcp_form_subnet_input": "サブネットマスク",
|
||||||
"dhcp_form_range_title": "IPアドレスの範囲",
|
"dhcp_form_range_title": "IPアドレスの範囲",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
|
"form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
|
||||||
"form_error_positive": "0보다 커야 합니다",
|
"form_error_positive": "0보다 커야 합니다",
|
||||||
"form_error_negative": "반드시 0 이상이여야 합니다",
|
"form_error_negative": "반드시 0 이상이여야 합니다",
|
||||||
|
"range_end_error": "입력 값은 범위의 시작 지점보다 큰 값 이여야 합니다.",
|
||||||
"dhcp_form_gateway_input": "게이트웨이 IP",
|
"dhcp_form_gateway_input": "게이트웨이 IP",
|
||||||
"dhcp_form_subnet_input": "서브넷 마스크",
|
"dhcp_form_subnet_input": "서브넷 마스크",
|
||||||
"dhcp_form_range_title": "IP 주소 범위",
|
"dhcp_form_range_title": "IP 주소 범위",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP IPv4 instellingen",
|
"dhcp_ipv4_settings": "DHCP IPv4 instellingen",
|
||||||
"dhcp_ipv6_settings": "DHCP IPv6 instellingen",
|
"dhcp_ipv6_settings": "DHCP IPv6 instellingen",
|
||||||
"form_error_required": "Vereist veld",
|
"form_error_required": "Vereist veld",
|
||||||
"form_error_ip4_format": "Ongeldig IPv4-adres",
|
"form_error_ip4_format": "Ongeldig IPv4 formaat",
|
||||||
"form_error_ip4_range_start_format": "Ongeldig IPv4-adres start bereik",
|
"form_error_ip6_format": "Ongeldig IPv6 formaat",
|
||||||
"form_error_ip4_range_end_format": "Ongeldig IPv4-adres einde bereik",
|
"form_error_ip_format": "Ongeldig IPv4 formaat",
|
||||||
"form_error_ip4_gateway_format": "Ongeldig IPv4-adres van de gateway",
|
"form_error_mac_format": "Ongeldig MAC formaat.",
|
||||||
"form_error_ip6_format": "Ongeldig IPv6-adres",
|
"form_error_client_id_format": "Opmaak cliënt-ID is ongeldig",
|
||||||
"form_error_ip_format": "Ongeldig IP-adres",
|
|
||||||
"form_error_mac_format": "Ongeldig MAC-adres",
|
|
||||||
"form_error_client_id_format": "Ongeldige cliënt-ID",
|
|
||||||
"form_error_server_name": "Ongeldige servernaam",
|
"form_error_server_name": "Ongeldige servernaam",
|
||||||
"form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
|
"form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
|
||||||
"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",
|
||||||
"out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Moet groter zijn dan het startbereik",
|
||||||
"lower_range_start_error": "Moet lager zijn dan begin reeks",
|
|
||||||
"greater_range_start_error": "Moet groter zijn dan begin reeks",
|
|
||||||
"greater_range_end_error": "Moet groter zijn dan einde reeks",
|
|
||||||
"subnet_error": "Adressen moeten in één subnet vallen",
|
|
||||||
"gateway_or_subnet_invalid": "Subnetmasker ongeldig",
|
|
||||||
"dhcp_form_gateway_input": "Gateway IP",
|
"dhcp_form_gateway_input": "Gateway IP",
|
||||||
"dhcp_form_subnet_input": "Subnet mask",
|
"dhcp_form_subnet_input": "Subnet mask",
|
||||||
"dhcp_form_range_title": "Bereik van IP adressen",
|
"dhcp_form_range_title": "Bereik van IP adressen",
|
||||||
@@ -215,7 +207,7 @@
|
|||||||
"example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0>",
|
"example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0>",
|
||||||
"example_upstream_sdns": "je kunt <0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen gebruiken",
|
"example_upstream_sdns": "je kunt <0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen gebruiken",
|
||||||
"example_upstream_tcp": "standaard DNS (over TCP)",
|
"example_upstream_tcp": "standaard DNS (over TCP)",
|
||||||
"all_lists_up_to_date_toast": "Alle lijsten zijn reeds actueel",
|
"all_lists_up_to_date_toast": "Alle lijsten zijn reeds up-to-date",
|
||||||
"updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
|
"updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
|
||||||
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
|
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
|
||||||
@@ -386,8 +378,8 @@
|
|||||||
"encryption_issuer": "Uitgever",
|
"encryption_issuer": "Uitgever",
|
||||||
"encryption_hostnames": "Hostnamen",
|
"encryption_hostnames": "Hostnamen",
|
||||||
"encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
|
"encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
|
||||||
"topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.",
|
"topline_expiring_certificate": "Jouw SSL certificaat vervalt binnenkort. Update <0>Encryptie instellingen</0>.",
|
||||||
"topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
|
"topline_expired_certificate": "Jouw SSL certificaat is vervallen. Update <0>Encryptie instellingen</0>.",
|
||||||
"form_error_port_range": "Poort nummer invoeren tussen 80 en 65535",
|
"form_error_port_range": "Poort nummer invoeren tussen 80 en 65535",
|
||||||
"form_error_port_unsafe": "Dit is een onveilige poort",
|
"form_error_port_unsafe": "Dit is een onveilige poort",
|
||||||
"form_error_equal": "Mag niet gelijk zijn",
|
"form_error_equal": "Mag niet gelijk zijn",
|
||||||
@@ -402,7 +394,7 @@
|
|||||||
"fix": "Los op",
|
"fix": "Los op",
|
||||||
"dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
|
"dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
|
||||||
"update_now": "Update nu",
|
"update_now": "Update nu",
|
||||||
"update_failed": "Automatisch bijwerken is mislukt. <a>Volg deze stappen</a> om handmatig bij te werken.",
|
"update_failed": "Auto-update is mislukt. <a>Volg deze stappen</a> om manueel te updaten.",
|
||||||
"processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
|
"processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
|
||||||
"clients_title": "Gebruikers",
|
"clients_title": "Gebruikers",
|
||||||
"clients_desc": "Configureer apparaten die gebruik maken van AdGuard Home",
|
"clients_desc": "Configureer apparaten die gebruik maken van AdGuard Home",
|
||||||
@@ -443,7 +435,7 @@
|
|||||||
"access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".",
|
"access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".",
|
||||||
"access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
|
"access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
|
||||||
"updates_checked": "Met succes op updates gecontroleerd",
|
"updates_checked": "Met succes op updates gecontroleerd",
|
||||||
"updates_version_equal": "AdGuard Home is actueel",
|
"updates_version_equal": "AdGuard Home is up-to-date",
|
||||||
"check_updates_now": "Controleer op updates",
|
"check_updates_now": "Controleer op updates",
|
||||||
"dns_privacy": "DNS Privacy",
|
"dns_privacy": "DNS Privacy",
|
||||||
"setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.",
|
"setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"form_error_server_name": "Ugyldig tjenernavn",
|
"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",
|
||||||
"dhcp_form_gateway_input": "Gateway-IP",
|
"dhcp_form_gateway_input": "Gateway-IP",
|
||||||
"dhcp_form_subnet_input": "Nettverksmaske",
|
"dhcp_form_subnet_input": "Nettverksmaske",
|
||||||
"dhcp_form_range_title": "Spennvidden til IP-adressene",
|
"dhcp_form_range_title": "Spennvidden til IP-adressene",
|
||||||
@@ -435,7 +436,6 @@
|
|||||||
"encryption_key_source_content": "Lim inn innholdet til den private nøkkelen",
|
"encryption_key_source_content": "Lim inn innholdet til den private nøkkelen",
|
||||||
"stats_params": "Statistikk-oppsett",
|
"stats_params": "Statistikk-oppsett",
|
||||||
"config_successfully_saved": "Oppsettet ble vellykket lagret",
|
"config_successfully_saved": "Oppsettet ble vellykket lagret",
|
||||||
"interval_6_hour": "6 timer",
|
|
||||||
"interval_24_hour": "24 timer",
|
"interval_24_hour": "24 timer",
|
||||||
"interval_days": "{{count}} dag",
|
"interval_days": "{{count}} dag",
|
||||||
"interval_days_plural": "{{count}} dager",
|
"interval_days_plural": "{{count}} dager",
|
||||||
@@ -555,5 +555,6 @@
|
|||||||
"click_to_view_queries": "Klikk for å vise forespørsler",
|
"click_to_view_queries": "Klikk for å vise forespørsler",
|
||||||
"port_53_faq_link": "Port 53 er ofte opptatt av «DNSStubListener»- eller «systemd-resolved»-tjenestene. Vennligst les <0>denne instruksjonen</0> om hvordan man løser dette.",
|
"port_53_faq_link": "Port 53 er ofte opptatt av «DNSStubListener»- eller «systemd-resolved»-tjenestene. Vennligst les <0>denne instruksjonen</0> om hvordan man løser dette.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home vil droppe alle DNS-forespørsler fra denne klienten.",
|
"adg_will_drop_dns_queries": "AdGuard Home vil droppe alle DNS-forespørsler fra denne klienten.",
|
||||||
|
"client_not_in_allowed_clients": "Klienten er ikke tillatt, fordi den ikke er i «Tillatte klienter»-listen.",
|
||||||
"experimental": "Eksperimentell"
|
"experimental": "Eksperimentell"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Ustawienia serwera DHCP IPv4",
|
"dhcp_ipv4_settings": "Ustawienia serwera DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Ustawienia serwera DHCP IPv6",
|
"dhcp_ipv6_settings": "Ustawienia serwera DHCP IPv6",
|
||||||
"form_error_required": "Pole jest wymagane",
|
"form_error_required": "Pole jest wymagane",
|
||||||
"form_error_ip4_format": "Nieprawidłowy adres IPv4",
|
"form_error_ip4_format": "Nieprawidłowy format IPv4",
|
||||||
"form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu",
|
"form_error_ip6_format": "Nieprawidłowy format IPv6",
|
||||||
"form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu",
|
"form_error_ip_format": "Nieprawidłowy format IP",
|
||||||
"form_error_ip4_gateway_format": "Nieprawidłowy adres IPv4 bramy",
|
"form_error_mac_format": "Nieprawidłowy format MAC",
|
||||||
"form_error_ip6_format": "Nieprawidłowy adres IPv6",
|
"form_error_client_id_format": "Nieprawidłowy format identyfikatora klienta",
|
||||||
"form_error_ip_format": "Nieprawidłowy adres IP",
|
|
||||||
"form_error_mac_format": "Nieprawidłowy adres MAC",
|
|
||||||
"form_error_client_id_format": "Nieprawidłowy ID klienta",
|
|
||||||
"form_error_server_name": "Nieprawidłowa nazwa serwera",
|
"form_error_server_name": "Nieprawidłowa nazwa serwera",
|
||||||
"form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
|
"form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Zakres musi być większy niż początkowy",
|
||||||
"lower_range_start_error": "Musi być niższy niż początek zakresu",
|
|
||||||
"greater_range_start_error": "Musi być większy niż początek zakresu",
|
|
||||||
"greater_range_end_error": "Musi być większy niż koniec zakresu",
|
|
||||||
"subnet_error": "Adresy muszą należeć do jednej podsieci",
|
|
||||||
"gateway_or_subnet_invalid": "Nieprawidłowa maska podsieci",
|
|
||||||
"dhcp_form_gateway_input": "Adres IP bramy",
|
"dhcp_form_gateway_input": "Adres IP bramy",
|
||||||
"dhcp_form_subnet_input": "Maska podsieci",
|
"dhcp_form_subnet_input": "Maska podsieci",
|
||||||
"dhcp_form_range_title": "Zakres adresów IP",
|
"dhcp_form_range_title": "Zakres adresów IP",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Configurações DHCP IPv4",
|
"dhcp_ipv4_settings": "Configurações DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Configurações DHCP IPv6",
|
"dhcp_ipv6_settings": "Configurações DHCP IPv6",
|
||||||
"form_error_required": "Campo obrigatório",
|
"form_error_required": "Campo obrigatório",
|
||||||
"form_error_ip4_format": "Endereço de IPv4 inválido",
|
"form_error_ip4_format": "Formato de endereço IPv4 inválido",
|
||||||
"form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
|
"form_error_ip6_format": "Formato de endereço IPv6 inválido",
|
||||||
"form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido",
|
"form_error_ip_format": "Formato de endereço IPv inválido",
|
||||||
"form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
|
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||||
"form_error_ip6_format": "Endereço de IPv6 inválido",
|
"form_error_client_id_format": "Formato do ID de cliente inválido",
|
||||||
"form_error_ip_format": "Endereço de IP inválido",
|
|
||||||
"form_error_mac_format": "Endereço de MAC inválido",
|
|
||||||
"form_error_client_id_format": "ID de cliente inválido",
|
|
||||||
"form_error_server_name": "Nome de servidor inválido",
|
"form_error_server_name": "Nome de servidor inválido",
|
||||||
"form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
|
"form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Deve ser maior que o início do intervalo",
|
||||||
"lower_range_start_error": "Deve ser inferior ao início do intervalo",
|
|
||||||
"greater_range_start_error": "Deve ser maior que o início do intervalo",
|
|
||||||
"greater_range_end_error": "Deve ser maior que o fim do intervalo",
|
|
||||||
"subnet_error": "Endereços devem estar em uma sub-rede",
|
|
||||||
"gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
|
|
||||||
"dhcp_form_gateway_input": "IP do gateway",
|
"dhcp_form_gateway_input": "IP do gateway",
|
||||||
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
||||||
"dhcp_form_range_title": "Faixa de endereços IP",
|
"dhcp_form_range_title": "Faixa de endereços IP",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Definições DHCP IPv4",
|
"dhcp_ipv4_settings": "Definições DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Definições DHCP IPv6",
|
"dhcp_ipv6_settings": "Definições DHCP IPv6",
|
||||||
"form_error_required": "Campo obrigatório",
|
"form_error_required": "Campo obrigatório",
|
||||||
"form_error_ip4_format": "Endereço de IPv4 inválido",
|
"form_error_ip4_format": "Formato de endereço IPv4 inválido",
|
||||||
"form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
|
"form_error_ip6_format": "Formato de endereço IPv6 inválido",
|
||||||
"form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido",
|
"form_error_ip_format": "Formato de endereço IPv4 inválido",
|
||||||
"form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
|
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||||
"form_error_ip6_format": "Endereço de IPv6 inválido",
|
"form_error_client_id_format": "Formato inválido",
|
||||||
"form_error_ip_format": "Endereço de IP inválido",
|
|
||||||
"form_error_mac_format": "Endereço de MAC inválido",
|
|
||||||
"form_error_client_id_format": "ID de cliente inválido",
|
|
||||||
"form_error_server_name": "Nome de servidor inválido",
|
"form_error_server_name": "Nome de servidor inválido",
|
||||||
"form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
|
"form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Deve ser maior que o início do intervalo",
|
||||||
"lower_range_start_error": "Deve ser inferior ao início do intervalo",
|
|
||||||
"greater_range_start_error": "Deve ser maior que o início do intervalo",
|
|
||||||
"greater_range_end_error": "Deve ser maior que o fim do intervalo",
|
|
||||||
"subnet_error": "Os endereços devem estar em uma sub-rede",
|
|
||||||
"gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
|
|
||||||
"dhcp_form_gateway_input": "IP do gateway",
|
"dhcp_form_gateway_input": "IP do gateway",
|
||||||
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
||||||
"dhcp_form_range_title": "Faixa de endereços IP",
|
"dhcp_form_range_title": "Faixa de endereços IP",
|
||||||
@@ -111,7 +103,7 @@
|
|||||||
"enabled_protection": "Ativar proteção",
|
"enabled_protection": "Ativar proteção",
|
||||||
"disable_protection": "Desativar proteção",
|
"disable_protection": "Desativar proteção",
|
||||||
"disabled_protection": "Desativar proteção",
|
"disabled_protection": "Desativar proteção",
|
||||||
"refresh_statics": "Actualizar estatísticas",
|
"refresh_statics": "Repor estatísticas",
|
||||||
"dns_query": "Consultas de DNS",
|
"dns_query": "Consultas de DNS",
|
||||||
"blocked_by": "<0>Bloqueado por filtros</0>",
|
"blocked_by": "<0>Bloqueado por filtros</0>",
|
||||||
"stats_malware_phishing": "Malware/phishing bloqueados",
|
"stats_malware_phishing": "Malware/phishing bloqueados",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Setări DHCP IPv4",
|
"dhcp_ipv4_settings": "Setări DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Setări DHCP IPv6",
|
"dhcp_ipv6_settings": "Setări DHCP IPv6",
|
||||||
"form_error_required": "Câmp necesar",
|
"form_error_required": "Câmp necesar",
|
||||||
"form_error_ip4_format": "Adresă IPv4 nevalidă",
|
"form_error_ip4_format": "Format IPv4 invalid",
|
||||||
"form_error_ip4_range_start_format": "Adresă IPv4 de început al intervalului nevalidă",
|
"form_error_ip6_format": "Format IPv6 invalid",
|
||||||
"form_error_ip4_range_end_format": "Adresa IPv4 de sfârșit al intervalului nevalidă",
|
"form_error_ip_format": "Format IP invalid",
|
||||||
"form_error_ip4_gateway_format": "Adresă IPv4 a gateway-ului nevalidă",
|
"form_error_mac_format": "Format MAC invalid",
|
||||||
"form_error_ip6_format": "Adresa IPv6 nevalidă",
|
"form_error_client_id_format": "Format ID de client invalid",
|
||||||
"form_error_ip_format": "Adresă IP nevalidă",
|
|
||||||
"form_error_mac_format": "Adresă MAC nevalidă",
|
|
||||||
"form_error_client_id_format": "ID client nevalid",
|
|
||||||
"form_error_server_name": "Nume de server nevalid",
|
"form_error_server_name": "Nume de server nevalid",
|
||||||
"form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
|
"form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
|
||||||
"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",
|
||||||
"out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”",
|
"range_end_error": "Trebuie să fie mai mare decât începutul intervalului",
|
||||||
"lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului",
|
|
||||||
"greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului",
|
|
||||||
"greater_range_end_error": "Trebuie să fie mai mare decât sfârșitul intervalului",
|
|
||||||
"subnet_error": "Adresele trebuie să fie în aceeași subrețea",
|
|
||||||
"gateway_or_subnet_invalid": "Mască de subrețea nevalidă",
|
|
||||||
"dhcp_form_gateway_input": "IP Gateway",
|
"dhcp_form_gateway_input": "IP Gateway",
|
||||||
"dhcp_form_subnet_input": "Mască subnet",
|
"dhcp_form_subnet_input": "Mască subnet",
|
||||||
"dhcp_form_range_title": "Interval de adrese IP",
|
"dhcp_form_range_title": "Interval de adrese IP",
|
||||||
@@ -120,8 +112,6 @@
|
|||||||
"for_last_24_hours": "în ultimele 24 ore",
|
"for_last_24_hours": "în ultimele 24 ore",
|
||||||
"for_last_days": "în ultima {{count}} zi",
|
"for_last_days": "în ultima {{count}} zi",
|
||||||
"for_last_days_plural": "pentru ultimele {{count}} zile",
|
"for_last_days_plural": "pentru ultimele {{count}} zile",
|
||||||
"stats_disabled": "Statisticile au fost dezactivate. Puteți să le porniți din <0>pagina de setări</0>.",
|
|
||||||
"stats_disabled_short": "Statisticile au fost dezactivate",
|
|
||||||
"no_domains_found": "Nu s-au găsit domenii",
|
"no_domains_found": "Nu s-au găsit domenii",
|
||||||
"requests_count": "Cont interogări",
|
"requests_count": "Cont interogări",
|
||||||
"top_blocked_domains": "Domeniile blocate cel mai des",
|
"top_blocked_domains": "Domeniile blocate cel mai des",
|
||||||
@@ -216,7 +206,7 @@
|
|||||||
"example_upstream_sdns": "puteți utiliza <0>DNS Stamps</0> pentru rezolvere <1>DNSCrypt</1> sau <2>DNS-over-HTTPS</2>",
|
"example_upstream_sdns": "puteți utiliza <0>DNS Stamps</0> pentru rezolvere <1>DNSCrypt</1> sau <2>DNS-over-HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS clasic (over TCP)",
|
"example_upstream_tcp": "DNS clasic (over TCP)",
|
||||||
"all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
|
"all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
|
||||||
"updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
|
"updated_upstream_dns_toast": "Serverele DNS în amonte aduse la zi",
|
||||||
"dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
|
"dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
|
||||||
"dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
|
"dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
|
||||||
"unblock": "Deblocați",
|
"unblock": "Deblocați",
|
||||||
@@ -243,7 +233,7 @@
|
|||||||
"loading_table_status": "Se încarcă...",
|
"loading_table_status": "Se încarcă...",
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "linii",
|
"rows_table_footer_text": "linii",
|
||||||
"updated_custom_filtering_toast": "Regulile personalizate au fost salvate cu succes",
|
"updated_custom_filtering_toast": "Reguli personalizate de filtrare aduse la zi",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare: {{rule}}",
|
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate: {{rule}}",
|
||||||
"query_log_response_status": "Statut: {{value}}",
|
"query_log_response_status": "Statut: {{value}}",
|
||||||
@@ -314,7 +304,7 @@
|
|||||||
"install_settings_dns_desc": "Va trebui să configurați aparatele sau routerul pentru a utiliza serverul DNS pe următoarele adrese:",
|
"install_settings_dns_desc": "Va trebui să configurați aparatele sau routerul pentru a utiliza serverul DNS pe următoarele adrese:",
|
||||||
"install_settings_all_interfaces": "Toate interfețele",
|
"install_settings_all_interfaces": "Toate interfețele",
|
||||||
"install_auth_title": "Autentificare",
|
"install_auth_title": "Autentificare",
|
||||||
"install_auth_desc": "Trebuie configurată autentificarea cu parolă la interfața web AdGuard Home admin. Chiar dacă AdGuard Home este accesibil numai în rețeaua locală, este important să îl protejați de accesul fără restricții.",
|
"install_auth_desc": "Este foarte recomandat să configurați o parolă pentru accesul la interfața web de administrare AdGuard Home. Chiar dacă este accesibil numai în rețeaua dvs. locală, este încă important să îl protejați de accesul fără restricții.",
|
||||||
"install_auth_username": "Nume utilizator",
|
"install_auth_username": "Nume utilizator",
|
||||||
"install_auth_password": "Parola",
|
"install_auth_password": "Parola",
|
||||||
"install_auth_confirm": "Confirmați parola",
|
"install_auth_confirm": "Confirmați parola",
|
||||||
@@ -337,7 +327,7 @@
|
|||||||
"install_devices_windows_list_3": "În partea stângă a ecranului găsiți \"Schimbare setări adaptor\" și clicați pe el.",
|
"install_devices_windows_list_3": "În partea stângă a ecranului găsiți \"Schimbare setări adaptor\" și clicați pe el.",
|
||||||
"install_devices_windows_list_4": "Selectați conexiunea activă, faceți clic dreapta pe ea și alegeți \"Proprietăți\".",
|
"install_devices_windows_list_4": "Selectați conexiunea activă, faceți clic dreapta pe ea și alegeți \"Proprietăți\".",
|
||||||
"install_devices_windows_list_5": "Găsiți Internet Protocol Versiunea 4 (TCP/IPv4) din listă, selectați-l și apoi clicați din nou pe Proprietăți.",
|
"install_devices_windows_list_5": "Găsiți Internet Protocol Versiunea 4 (TCP/IPv4) din listă, selectați-l și apoi clicați din nou pe Proprietăți.",
|
||||||
"install_devices_windows_list_6": "Alegeți „Utilizați următoarele adrese de server DNS” și introduceți adresele serverului dvs. AdGuard Home.",
|
"install_devices_windows_list_6": "Alegeți Utilizați următoarele adrese de server DNS și introduceți adresele de server AdGuard Home.",
|
||||||
"install_devices_macos_list_1": "Clicați pe icoana Apple și accesați Preferințele Sistemului.",
|
"install_devices_macos_list_1": "Clicați pe icoana Apple și accesați Preferințele Sistemului.",
|
||||||
"install_devices_macos_list_2": "Clicați pe Network.",
|
"install_devices_macos_list_2": "Clicați pe Network.",
|
||||||
"install_devices_macos_list_3": "Selectați prima conexiune din listă și clicați pe Avansat.",
|
"install_devices_macos_list_3": "Selectați prima conexiune din listă și clicați pe Avansat.",
|
||||||
@@ -511,7 +501,6 @@
|
|||||||
"statistics_clear_confirm": "Sunteți sigur că doriți să ștergeți statisticile?",
|
"statistics_clear_confirm": "Sunteți sigur că doriți să ștergeți statisticile?",
|
||||||
"statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
|
"statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
|
||||||
"statistics_cleared": "Statisticile au fost șterse cu succes",
|
"statistics_cleared": "Statisticile au fost șterse cu succes",
|
||||||
"statistics_enable": "Activați statisticile",
|
|
||||||
"interval_hours": "{{count}} oră",
|
"interval_hours": "{{count}} oră",
|
||||||
"interval_hours_plural": "{{count}} ore",
|
"interval_hours_plural": "{{count}} ore",
|
||||||
"filters_configuration": "Configurația filtrelor",
|
"filters_configuration": "Configurația filtrelor",
|
||||||
@@ -606,8 +595,6 @@
|
|||||||
"cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS",
|
"cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS",
|
||||||
"cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS",
|
"cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS",
|
||||||
"ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă",
|
"ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă",
|
||||||
"cache_optimistic": "Caching optimistic",
|
|
||||||
"cache_optimistic_desc": "Face ca AdGuard Home să răspundă din cache chiar și atunci când intrările au expirate și de asemenea, încearcă să le reîmprospăteze.",
|
|
||||||
"filter_category_general": "General",
|
"filter_category_general": "General",
|
||||||
"filter_category_security": "Securitate",
|
"filter_category_security": "Securitate",
|
||||||
"filter_category_regional": "Regional",
|
"filter_category_regional": "Regional",
|
||||||
@@ -621,8 +608,6 @@
|
|||||||
"click_to_view_queries": "Clicați pentru a vizualiza interogări",
|
"click_to_view_queries": "Clicați pentru a vizualiza interogări",
|
||||||
"port_53_faq_link": "Portul 53 este adesea ocupat de serviciile \"DNSStubListener\" sau \"systemd-resolved\". Vă rugăm să citiți <0>această instrucțiune</0> despre cum să rezolvați aceasta.",
|
"port_53_faq_link": "Portul 53 este adesea ocupat de serviciile \"DNSStubListener\" sau \"systemd-resolved\". Vă rugăm să citiți <0>această instrucțiune</0> despre cum să rezolvați aceasta.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home va renunța la toate interogările DNS de la acest client.",
|
"adg_will_drop_dns_queries": "AdGuard Home va renunța la toate interogările DNS de la acest client.",
|
||||||
"filter_allowlist": "AVERTISMENT: Această acțiune va exclude și regula „{{disallowed_rule}}” din lista de clienți permiși.",
|
"client_not_in_allowed_clients": "Clientul nu este permis deoarece nu este în lista de \"Clienți permiși\".",
|
||||||
"last_rule_in_allowlist": "Acest client nu poate fi exclus deoarece excluderea regulii „{{disallowed_rule}}” va DEZACTIVA lista „Clienți acceptați”.",
|
"experimental": "Experimental"
|
||||||
"experimental": "Experimental",
|
|
||||||
"use_saved_key": "Folosiți cheia salvată anterior"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Настройки DHCP IPv4",
|
"dhcp_ipv4_settings": "Настройки DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Настройки DHCP IPv6",
|
"dhcp_ipv6_settings": "Настройки DHCP IPv6",
|
||||||
"form_error_required": "Обязательное поле",
|
"form_error_required": "Обязательное поле",
|
||||||
"form_error_ip4_format": "Неверный IPv4-адрес",
|
"form_error_ip4_format": "Неверный формат IPv4",
|
||||||
"form_error_ip4_range_start_format": "Неверный IPv4-адрес начала диапазона",
|
"form_error_ip6_format": "Неверный формат IPv6",
|
||||||
"form_error_ip4_range_end_format": "Неверный IPv4-адрес конца диапазона",
|
"form_error_ip_format": "Неверный формат IP-адреса",
|
||||||
"form_error_ip4_gateway_format": "Неверный IPv4-адрес шлюза",
|
"form_error_mac_format": "Некорректный формат MAC",
|
||||||
"form_error_ip6_format": "Неверный IPv6-адрес",
|
"form_error_client_id_format": "Неверный формат ID клиента",
|
||||||
"form_error_ip_format": "Неверный IP-адрес",
|
|
||||||
"form_error_mac_format": "Неверный MAC-адрес",
|
|
||||||
"form_error_client_id_format": "Неверный ID клиента",
|
|
||||||
"form_error_server_name": "Неверное имя сервера",
|
"form_error_server_name": "Неверное имя сервера",
|
||||||
"form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
|
"form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
|
||||||
"form_error_positive": "Должно быть больше 0",
|
"form_error_positive": "Должно быть больше 0",
|
||||||
"form_error_negative": "Должно быть не меньше 0",
|
"form_error_negative": "Должно быть не меньше 0",
|
||||||
"out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}»",
|
"range_end_error": "Должно превышать начало диапазона",
|
||||||
"lower_range_start_error": "Должно быть меньше начала диапазона",
|
|
||||||
"greater_range_start_error": "Должно быть больше начала диапазона",
|
|
||||||
"greater_range_end_error": "Должно быть больше конца диапазона",
|
|
||||||
"subnet_error": "Адреса должны быть внутри одной подсети",
|
|
||||||
"gateway_or_subnet_invalid": "Некорректная маска подсети",
|
|
||||||
"dhcp_form_gateway_input": "IP-адрес шлюза",
|
"dhcp_form_gateway_input": "IP-адрес шлюза",
|
||||||
"dhcp_form_subnet_input": "Маска подсети",
|
"dhcp_form_subnet_input": "Маска подсети",
|
||||||
"dhcp_form_range_title": "Диапазон IP-адресов",
|
"dhcp_form_range_title": "Диапазон IP-адресов",
|
||||||
@@ -239,7 +231,7 @@
|
|||||||
"no_logs_found": "Логи не найдены",
|
"no_logs_found": "Логи не найдены",
|
||||||
"refresh_btn": "Обновить",
|
"refresh_btn": "Обновить",
|
||||||
"previous_btn": "Назад",
|
"previous_btn": "Назад",
|
||||||
"next_btn": "Далее",
|
"next_btn": "Вперёд",
|
||||||
"loading_table_status": "Загрузка…",
|
"loading_table_status": "Загрузка…",
|
||||||
"page_table_footer_text": "Страница",
|
"page_table_footer_text": "Страница",
|
||||||
"rows_table_footer_text": "строк",
|
"rows_table_footer_text": "строк",
|
||||||
@@ -352,7 +344,7 @@
|
|||||||
"install_devices_ios_list_3": "Нажмите на название сети, к которой устройство подключено в данный момент.",
|
"install_devices_ios_list_3": "Нажмите на название сети, к которой устройство подключено в данный момент.",
|
||||||
"install_devices_ios_list_4": "В поле «DNS» введите введите адреса AdGuard Home.",
|
"install_devices_ios_list_4": "В поле «DNS» введите введите адреса AdGuard Home.",
|
||||||
"get_started": "Поехали",
|
"get_started": "Поехали",
|
||||||
"next": "Далее",
|
"next": "Дальше",
|
||||||
"open_dashboard": "Открыть Панель управления",
|
"open_dashboard": "Открыть Панель управления",
|
||||||
"install_saved": "Успешно сохранено",
|
"install_saved": "Успешно сохранено",
|
||||||
"encryption_title": "Шифрование",
|
"encryption_title": "Шифрование",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Nastavenia DHCP IPv4",
|
"dhcp_ipv4_settings": "Nastavenia DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Nastavenia DHCP IPv6",
|
"dhcp_ipv6_settings": "Nastavenia DHCP IPv6",
|
||||||
"form_error_required": "Povinná položka",
|
"form_error_required": "Povinná položka",
|
||||||
"form_error_ip4_format": "Neplatná IPv4 adresa",
|
"form_error_ip4_format": "Nesprávny formát IPv4",
|
||||||
"form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu",
|
"form_error_ip6_format": "Nesprávny formát IPv6",
|
||||||
"form_error_ip4_range_end_format": "Neplatný koniec rozsahu IPv4 formátu",
|
"form_error_ip_format": "Nesprávny formát IPv4",
|
||||||
"form_error_ip4_gateway_format": "Neplatná IPv4 adresa brány",
|
"form_error_mac_format": "Nesprávny MAC formát",
|
||||||
"form_error_ip6_format": "Neplatná IPv6 adresa",
|
"form_error_client_id_format": "Neplatný formát client ID",
|
||||||
"form_error_ip_format": "Neplatná IP adresa",
|
|
||||||
"form_error_mac_format": "Neplatná MAC adresa",
|
|
||||||
"form_error_client_id_format": "Neplatné ID klienta",
|
|
||||||
"form_error_server_name": "Neplatné meno servera",
|
"form_error_server_name": "Neplatné meno servera",
|
||||||
"form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
"form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Musí byť väčšie ako začiatok rozsahu",
|
||||||
"lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu",
|
|
||||||
"greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu",
|
|
||||||
"greater_range_end_error": "Musí byť väčšie ako koniec rozsahu",
|
|
||||||
"subnet_error": "Adresy musia byť v spoločnej podsieti",
|
|
||||||
"gateway_or_subnet_invalid": "Maska podsiete je neplatná",
|
|
||||||
"dhcp_form_gateway_input": "IP brána",
|
"dhcp_form_gateway_input": "IP brána",
|
||||||
"dhcp_form_subnet_input": "Maska podsiete",
|
"dhcp_form_subnet_input": "Maska podsiete",
|
||||||
"dhcp_form_range_title": "Rozsah IP adries",
|
"dhcp_form_range_title": "Rozsah IP adries",
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "Nastavitve DHCP IPv4",
|
"dhcp_ipv4_settings": "Nastavitve DHCP IPv4",
|
||||||
"dhcp_ipv6_settings": "Nastavitve DHCP IPv6",
|
"dhcp_ipv6_settings": "Nastavitve DHCP IPv6",
|
||||||
"form_error_required": "Zahtevano polje",
|
"form_error_required": "Zahtevano polje",
|
||||||
"form_error_ip4_format": "Neveljaven naslov IPv4",
|
"form_error_ip4_format": "Neveljaven format IPv4",
|
||||||
"form_error_ip4_range_start_format": "Neveljaven začetek oblike razpona IPv4",
|
"form_error_ip6_format": "Neveljaven format IPv6",
|
||||||
"form_error_ip4_range_end_format": "Neveljaven konec oblike razpona IPv4",
|
"form_error_ip_format": "Neveljaven format IP",
|
||||||
"form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda",
|
"form_error_mac_format": "Neveljaven MAC format",
|
||||||
"form_error_ip6_format": "Neveljaven naslov IPv6",
|
"form_error_client_id_format": "Neveljaven format ID odjemalca",
|
||||||
"form_error_ip_format": "Neveljaven naslov IP",
|
|
||||||
"form_error_mac_format": "Neveljaven naslov MAC",
|
|
||||||
"form_error_client_id_format": "Neveljaven ID odjemalca",
|
|
||||||
"form_error_server_name": "Neveljavno ime strežnika",
|
"form_error_server_name": "Neveljavno ime strežnika",
|
||||||
"form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
|
"form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
|
||||||
"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",
|
||||||
"out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"",
|
"range_end_error": "Mora biti večji od začtka razpona",
|
||||||
"lower_range_start_error": "Mora biti manjši od začetka razpona",
|
|
||||||
"greater_range_start_error": "Mora biti večji od začetka razpona",
|
|
||||||
"greater_range_end_error": "Mora biti večji od konca razpona",
|
|
||||||
"subnet_error": "Naslovi morajo biti v enem podomrežju",
|
|
||||||
"gateway_or_subnet_invalid": "Maska podomrežja ni veljavna",
|
|
||||||
"dhcp_form_gateway_input": "IP prehoda",
|
"dhcp_form_gateway_input": "IP prehoda",
|
||||||
"dhcp_form_subnet_input": "Maska podomrežja",
|
"dhcp_form_subnet_input": "Maska podomrežja",
|
||||||
"dhcp_form_range_title": "Razpon naslovov IP",
|
"dhcp_form_range_title": "Razpon naslovov IP",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"form_error_client_id_format": "Nevažeći format klijenta",
|
"form_error_client_id_format": "Nevažeći format klijenta",
|
||||||
"form_error_positive": "Mora biti veće od 0",
|
"form_error_positive": "Mora biti veće od 0",
|
||||||
"form_error_negative": "Mora biti 0 ili veće",
|
"form_error_negative": "Mora biti 0 ili veće",
|
||||||
|
"range_end_error": "Mora biti veće od početnog opsega",
|
||||||
"dhcp_form_gateway_input": "IP mrežnog prolaza",
|
"dhcp_form_gateway_input": "IP mrežnog prolaza",
|
||||||
"dhcp_form_subnet_input": "Subnet mask",
|
"dhcp_form_subnet_input": "Subnet mask",
|
||||||
"dhcp_form_range_title": "Opseg IP adresa",
|
"dhcp_form_range_title": "Opseg IP adresa",
|
||||||
@@ -225,10 +226,8 @@
|
|||||||
"custom_ip": "Prilagođeni IP",
|
"custom_ip": "Prilagođeni 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",
|
|
||||||
"download_mobileconfig_doh": "Preuzimanja",
|
"download_mobileconfig_doh": "Preuzimanja",
|
||||||
"download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
|
"download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
|
||||||
"plain_dns": "Plain DNS",
|
"plain_dns": "Plain DNS",
|
||||||
@@ -431,7 +430,6 @@
|
|||||||
"encryption_key_source_content": "Nalepi sadržaj privatnog ključa",
|
"encryption_key_source_content": "Nalepi sadržaj privatnog ključa",
|
||||||
"stats_params": "Konfiguracija statistike",
|
"stats_params": "Konfiguracija statistike",
|
||||||
"config_successfully_saved": "Konfiguracija je uspešno sačuvana",
|
"config_successfully_saved": "Konfiguracija je uspešno sačuvana",
|
||||||
"interval_6_hour": "6 sati",
|
|
||||||
"interval_24_hour": "24 časa",
|
"interval_24_hour": "24 časa",
|
||||||
"interval_days": "{{count}} dan",
|
"interval_days": "{{count}} dan",
|
||||||
"interval_days_plural": "{{count}} dana",
|
"interval_days_plural": "{{count}} dana",
|
||||||
@@ -551,5 +549,6 @@
|
|||||||
"click_to_view_queries": "Kliknite da pogledate zahteve",
|
"click_to_view_queries": "Kliknite da pogledate zahteve",
|
||||||
"port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite.",
|
"port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home će odbacivati sve DNS unose od ovog klijenta.",
|
"adg_will_drop_dns_queries": "AdGuard Home će odbacivati sve DNS unose od ovog klijenta.",
|
||||||
|
"client_not_in_allowed_clients": "Klijent nije dozvoljen zato što se ne nalazi na spisku dozvoljenih klijenata.",
|
||||||
"experimental": "Eksperimentalno"
|
"experimental": "Eksperimentalno"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"dhcp_static_leases": "Statiska DHCP-leases",
|
"dhcp_static_leases": "Statiska DHCP-leases",
|
||||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||||
"form_error_required": "Obligatoriskt fält",
|
"form_error_required": "Obligatoriskt fält",
|
||||||
"form_error_ip_format": "Ogiltig IP-adress",
|
"form_error_ip_format": "Ogiltigt IPv4-format",
|
||||||
"form_error_mac_format": "Ogiltig MAC-adress",
|
"form_error_mac_format": "Ogiltigt MAC-format",
|
||||||
"form_error_positive": "Måste vara större än noll",
|
"form_error_positive": "Måste vara större än noll",
|
||||||
"dhcp_form_gateway_input": "Gateway-IP",
|
"dhcp_form_gateway_input": "Gateway-IP",
|
||||||
"dhcp_form_subnet_input": "Subnetmask",
|
"dhcp_form_subnet_input": "Subnetmask",
|
||||||
@@ -39,18 +39,14 @@
|
|||||||
"delete_confirm": "Är du säker på att du vill ta bort \"{{key}}\"?",
|
"delete_confirm": "Är du säker på att du vill ta bort \"{{key}}\"?",
|
||||||
"form_enter_hostname": "Skriv in värdnamn",
|
"form_enter_hostname": "Skriv in värdnamn",
|
||||||
"error_details": "Felinformation",
|
"error_details": "Felinformation",
|
||||||
"request_details": "Förfrågningsdetaljer",
|
|
||||||
"details": "Detaljer",
|
|
||||||
"back": "Tiilbaka",
|
"back": "Tiilbaka",
|
||||||
"dashboard": "Kontrollpanel",
|
"dashboard": "Kontrollpanel",
|
||||||
"settings": "Inställningar",
|
"settings": "Inställningar",
|
||||||
"filters": "Filter",
|
"filters": "Filter",
|
||||||
"filter": "Filter",
|
|
||||||
"query_log": "Förfrågningslogg",
|
"query_log": "Förfrågningslogg",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"version": "version",
|
"version": "version",
|
||||||
"address": "Adress",
|
"address": "Adress",
|
||||||
"protocol": "Protokoll",
|
|
||||||
"on": "PÅ",
|
"on": "PÅ",
|
||||||
"off": "AV",
|
"off": "AV",
|
||||||
"copyright": "Copyright",
|
"copyright": "Copyright",
|
||||||
@@ -89,7 +85,6 @@
|
|||||||
"no_servers_specified": "Inga servrar angivna",
|
"no_servers_specified": "Inga servrar angivna",
|
||||||
"general_settings": "Allmänna inställningar",
|
"general_settings": "Allmänna inställningar",
|
||||||
"dns_settings": "DNS-inställningar",
|
"dns_settings": "DNS-inställningar",
|
||||||
"custom_filtering_rules": "Egna filterregler",
|
|
||||||
"encryption_settings": "Krypteringsinställningar",
|
"encryption_settings": "Krypteringsinställningar",
|
||||||
"dhcp_settings": "DHCP-inställningar",
|
"dhcp_settings": "DHCP-inställningar",
|
||||||
"upstream_dns": "Upstream DNS-servrar",
|
"upstream_dns": "Upstream DNS-servrar",
|
||||||
@@ -164,12 +159,6 @@
|
|||||||
"query_log_disabled": "Förfrågningsloggen är avaktiverad och kan konfigureras i <0>inställningar</0>",
|
"query_log_disabled": "Förfrågningsloggen är avaktiverad och kan konfigureras i <0>inställningar</0>",
|
||||||
"query_log_strict_search": "Använd dubbla citattecken för strikt sökning",
|
"query_log_strict_search": "Använd dubbla citattecken för strikt sökning",
|
||||||
"query_log_retention_confirm": "Är du säker på att du vill ändra förfrågningsloggars retentionstid? Om du minskar intervallet kommer viss data att gå förlorad",
|
"query_log_retention_confirm": "Är du säker på att du vill ändra förfrågningsloggars retentionstid? Om du minskar intervallet kommer viss data att gå förlorad",
|
||||||
"default": "Standard",
|
|
||||||
"custom_ip": "Eget IP",
|
|
||||||
"dnscrypt": "DNSCrypt",
|
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
|
||||||
"dns_over_quic": "DNS-over-QUIC",
|
|
||||||
"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",
|
||||||
@@ -344,20 +333,12 @@
|
|||||||
"location": "Plats",
|
"location": "Plats",
|
||||||
"orgname": "Organisationsnamn",
|
"orgname": "Organisationsnamn",
|
||||||
"netname": "Nätverksnamn",
|
"netname": "Nätverksnamn",
|
||||||
"network": "Nätverk",
|
|
||||||
"descr": "Beskrivning",
|
"descr": "Beskrivning",
|
||||||
"whois": "Whois",
|
"whois": "Whois",
|
||||||
"filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar.",
|
"filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar.",
|
||||||
"try_again": "Försök igen",
|
"try_again": "Försök igen",
|
||||||
"show_blocked_responses": "Blockerade",
|
"show_blocked_responses": "Blockerade",
|
||||||
"show_whitelisted_responses": "Vitlistade",
|
|
||||||
"show_processed_responses": "Utförda",
|
|
||||||
"blocked_adult_websites": "Blockerade vuxensajter",
|
"blocked_adult_websites": "Blockerade vuxensajter",
|
||||||
"blocked_threats": "Blockerade hot",
|
"blocked_threats": "Blockerade hot",
|
||||||
"allowed": "Vitlistade",
|
|
||||||
"safe_search": "Säker surf",
|
|
||||||
"filter_category_general": "General",
|
|
||||||
"filter_category_security": "säkerhet",
|
|
||||||
"filter_category_other": "Annat",
|
|
||||||
"use_saved_key": "Använd den tidigare sparade nyckeln"
|
"use_saved_key": "Använd den tidigare sparade nyckeln"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"delete_confirm": "คุณแน่ใจหรือว่าต้องการลบ \"{{key}}\"?",
|
"delete_confirm": "คุณแน่ใจหรือว่าต้องการลบ \"{{key}}\"?",
|
||||||
"form_enter_hostname": "ป้อนชื่อโฮสต์",
|
"form_enter_hostname": "ป้อนชื่อโฮสต์",
|
||||||
"error_details": "รายละเอียดข้อผิดพลาด",
|
"error_details": "รายละเอียดข้อผิดพลาด",
|
||||||
"request_details": "ขอรายละเอียด",
|
|
||||||
"back": "กลับ",
|
"back": "กลับ",
|
||||||
"dashboard": "แผงควบคุม",
|
"dashboard": "แผงควบคุม",
|
||||||
"settings": "การตั้งค่า",
|
"settings": "การตั้งค่า",
|
||||||
@@ -88,7 +87,6 @@
|
|||||||
"no_servers_specified": "ไม่ได้ระบุเซิร์ฟเวอร์",
|
"no_servers_specified": "ไม่ได้ระบุเซิร์ฟเวอร์",
|
||||||
"general_settings": "การตั้งค่าทั่วไป",
|
"general_settings": "การตั้งค่าทั่วไป",
|
||||||
"dns_settings": "การตั้งค่า DNS",
|
"dns_settings": "การตั้งค่า DNS",
|
||||||
"custom_filtering_rules": "กฎการกรองที่กำหนดเอง",
|
|
||||||
"encryption_settings": "การตั้งค่าการเข้ารหัส",
|
"encryption_settings": "การตั้งค่าการเข้ารหัส",
|
||||||
"dhcp_settings": "การตั้งค่า DHCP",
|
"dhcp_settings": "การตั้งค่า DHCP",
|
||||||
"upstream_dns": "เซิร์ฟเวอร์ DNS ต้นทาง",
|
"upstream_dns": "เซิร์ฟเวอร์ DNS ต้นทาง",
|
||||||
@@ -171,9 +169,6 @@
|
|||||||
"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_tls": "DNS-over-TLS",
|
|
||||||
"form_enter_rate_limit": "ป้อนขีดจำกัดอัตรา",
|
"form_enter_rate_limit": "ป้อนขีดจำกัดอัตรา",
|
||||||
"rate_limit": "จำกัดอัตรา",
|
"rate_limit": "จำกัดอัตรา",
|
||||||
"edns_enable": "เปิดใช้งานซับเน็ตไคลเอ็นต์ EDNS",
|
"edns_enable": "เปิดใช้งานซับเน็ตไคลเอ็นต์ EDNS",
|
||||||
@@ -338,7 +333,6 @@
|
|||||||
"encryption_key_source_content": "วางเนื้อหาคีย์ส่วนตัว",
|
"encryption_key_source_content": "วางเนื้อหาคีย์ส่วนตัว",
|
||||||
"stats_params": "การกำหนดค่าสถิติ",
|
"stats_params": "การกำหนดค่าสถิติ",
|
||||||
"config_successfully_saved": "บันทึกการตั้งค่าเรีบยร้อยแล้ว",
|
"config_successfully_saved": "บันทึกการตั้งค่าเรีบยร้อยแล้ว",
|
||||||
"interval_6_hour": "6 ชั่วโมง",
|
|
||||||
"interval_24_hour": "24 ชั่วโมง",
|
"interval_24_hour": "24 ชั่วโมง",
|
||||||
"interval_days": "{{count}} วัน",
|
"interval_days": "{{count}} วัน",
|
||||||
"interval_days_plural": "{{count}} วัน",
|
"interval_days_plural": "{{count}} วัน",
|
||||||
@@ -389,8 +383,5 @@
|
|||||||
"form_select_tags": "เลือกแท็กเครื่อง",
|
"form_select_tags": "เลือกแท็กเครื่อง",
|
||||||
"check_title": "ตรวจสอบการกรอง",
|
"check_title": "ตรวจสอบการกรอง",
|
||||||
"check_desc": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง",
|
"check_desc": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง",
|
||||||
"form_enter_host": "ป้อนชื่อโฮสต์",
|
"form_enter_host": "ป้อนชื่อโฮสต์"
|
||||||
"show_processed_responses": "การประมวลผล",
|
|
||||||
"safe_search": "ค้นหาอย่างปลอดภัย",
|
|
||||||
"filter_category_other": "อื่น ๆ"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,29 +36,21 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
|
"dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
|
||||||
"dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
|
"dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
|
||||||
"form_error_required": "Gerekli alan",
|
"form_error_required": "Gerekli alan",
|
||||||
"form_error_ip4_format": "Geçersiz IPv4 adresi",
|
"form_error_ip4_format": "Geçersiz IPv4 biçimi",
|
||||||
"form_error_ip4_range_start_format": "Başlangıç aralığı IPv4 adresi geçersiz",
|
"form_error_ip6_format": "Geçersiz IPv6 biçimi",
|
||||||
"form_error_ip4_range_end_format": "Bitiş aralığı IPv4 adresi geçersiz",
|
"form_error_ip_format": "Geçersiz IP biçimi",
|
||||||
"form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz",
|
"form_error_mac_format": "Geçersiz MAC biçimi",
|
||||||
"form_error_ip6_format": "Geçersiz IPv6 adresi",
|
"form_error_client_id_format": "Geçersiz istemci kimliği biçimi",
|
||||||
"form_error_ip_format": "Geçersiz IP adresi",
|
|
||||||
"form_error_mac_format": "Geçersiz MAC adresi",
|
|
||||||
"form_error_client_id_format": "Geçersiz istemci kimliği",
|
|
||||||
"form_error_server_name": "Geçersiz sunucu adı",
|
"form_error_server_name": "Geçersiz sunucu adı",
|
||||||
"form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
|
"form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
|
||||||
"form_error_positive": "0'dan büyük olmalıdır",
|
"form_error_positive": "0'dan büyük olmalıdır",
|
||||||
"form_error_negative": "0 veya daha büyük olmalıdır",
|
"form_error_negative": "0 veya daha büyük olmalıdır",
|
||||||
"out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır",
|
"range_end_error": "Başlangıç aralığından daha büyük olmalı",
|
||||||
"lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır",
|
|
||||||
"greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır",
|
|
||||||
"greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır",
|
|
||||||
"subnet_error": "Adresler bir alt ağda olmalıdır",
|
|
||||||
"gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz",
|
|
||||||
"dhcp_form_gateway_input": "Ağ Geçidi IP'si",
|
"dhcp_form_gateway_input": "Ağ Geçidi IP'si",
|
||||||
"dhcp_form_subnet_input": "Alt ağ maskesi",
|
"dhcp_form_subnet_input": "Alt ağ maskesi",
|
||||||
"dhcp_form_range_title": "IP adresi aralığı",
|
"dhcp_form_range_title": "IP adresi aralığı",
|
||||||
"dhcp_form_range_start": "Başlangıç aralığı",
|
"dhcp_form_range_start": "Aralık başlangıcı",
|
||||||
"dhcp_form_range_end": "Bitiş aralığı",
|
"dhcp_form_range_end": "Aralık sonu",
|
||||||
"dhcp_form_lease_title": "DHCP kira süresi (saniye olarak)",
|
"dhcp_form_lease_title": "DHCP kira süresi (saniye olarak)",
|
||||||
"dhcp_form_lease_input": "Kira süresi",
|
"dhcp_form_lease_input": "Kira süresi",
|
||||||
"dhcp_interface_select": "DHCP arayüzünü seç",
|
"dhcp_interface_select": "DHCP arayüzünü seç",
|
||||||
@@ -66,7 +58,7 @@
|
|||||||
"dhcp_ip_addresses": "IP adresleri",
|
"dhcp_ip_addresses": "IP adresleri",
|
||||||
"ip": "IP",
|
"ip": "IP",
|
||||||
"dhcp_table_hostname": "Bilgisayar Adı",
|
"dhcp_table_hostname": "Bilgisayar Adı",
|
||||||
"dhcp_table_expires": "Bitiş tarihi",
|
"dhcp_table_expires": "Geçerlilik Tarihi",
|
||||||
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların İnternet bağlantısı kesilebilir!",
|
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların İnternet bağlantısı kesilebilir!",
|
||||||
"dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi.",
|
"dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi.",
|
||||||
"dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
|
"dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
|
||||||
@@ -324,7 +316,7 @@
|
|||||||
"install_devices_title": "Cihazlarınızı yapılandırın",
|
"install_devices_title": "Cihazlarınızı yapılandırın",
|
||||||
"install_devices_desc": "AdGuard Home'u kullanmaya başlamak için, cihazlarınızı onu kullanacak şekilde yapılandırmanız gerekir.",
|
"install_devices_desc": "AdGuard Home'u kullanmaya başlamak için, cihazlarınızı onu kullanacak şekilde yapılandırmanız gerekir.",
|
||||||
"install_submit_title": "Tebrikler!",
|
"install_submit_title": "Tebrikler!",
|
||||||
"install_submit_desc": "Yükleme işlemi tamamlandı ve artık AdGuard Home'u kullanmaya hazırsınız.",
|
"install_submit_desc": "Kurulum işlemi tamamlandı ve artık AdGuard Home'u kullanmaya hazırsınız.",
|
||||||
"install_devices_router": "Yönlendirici",
|
"install_devices_router": "Yönlendirici",
|
||||||
"install_devices_router_desc": "Bu kurulum, ev yönlendiricinize bağlı tüm cihazları otomatik olarak kapsar ve her birini elle yapılandırmanıza gerek yoktur.",
|
"install_devices_router_desc": "Bu kurulum, ev yönlendiricinize bağlı tüm cihazları otomatik olarak kapsar ve her birini elle yapılandırmanıza gerek yoktur.",
|
||||||
"install_devices_address": "AdGuard Home DNS sunucusu şu adresi dinleyecektir",
|
"install_devices_address": "AdGuard Home DNS sunucusu şu adresi dinleyecektir",
|
||||||
@@ -509,7 +501,7 @@
|
|||||||
"statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
|
"statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
|
||||||
"statistics_clear": " İstatistikleri temizle",
|
"statistics_clear": " İstatistikleri temizle",
|
||||||
"statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
|
"statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
|
||||||
"statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
|
"statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
|
||||||
"statistics_cleared": "İstatistikler başarıyla temizlendi",
|
"statistics_cleared": "İstatistikler başarıyla temizlendi",
|
||||||
"statistics_enable": "İstatistikleri etkinleştir",
|
"statistics_enable": "İstatistikleri etkinleştir",
|
||||||
"interval_hours": "{{count}} saat",
|
"interval_hours": "{{count}} saat",
|
||||||
@@ -584,13 +576,13 @@
|
|||||||
"dnssec_enable_desc": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol et (DNSSEC özellikli çözümleyici gerekli).",
|
"dnssec_enable_desc": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol et (DNSSEC özellikli çözümleyici gerekli).",
|
||||||
"validated_with_dnssec": "DNSSEC ile doğrulandı",
|
"validated_with_dnssec": "DNSSEC ile doğrulandı",
|
||||||
"all_queries": "Tüm sorgular",
|
"all_queries": "Tüm sorgular",
|
||||||
"show_blocked_responses": "Engellenen",
|
"show_blocked_responses": "Engellendi",
|
||||||
"show_whitelisted_responses": "İzin verilen",
|
"show_whitelisted_responses": "İzin verilen",
|
||||||
"show_processed_responses": "İşlenen",
|
"show_processed_responses": "İşlendi",
|
||||||
"blocked_safebrowsing": "Güvenli gezinti tarafından engellendi",
|
"blocked_safebrowsing": "Güvenli gezinti tarafından engellendi",
|
||||||
"blocked_adult_websites": "Engellenen yetişkin içerikli siteler",
|
"blocked_adult_websites": "Engellenen yetişkin içerikli siteler",
|
||||||
"blocked_threats": "Engellenen tehditler",
|
"blocked_threats": "Engellenen tehditler",
|
||||||
"allowed": "İzin verilen",
|
"allowed": "İzin verildi",
|
||||||
"filtered": "Filtrelenen",
|
"filtered": "Filtrelenen",
|
||||||
"rewritten": "Yeniden yazılan",
|
"rewritten": "Yeniden yazılan",
|
||||||
"safe_search": "Güvenli arama",
|
"safe_search": "Güvenli arama",
|
||||||
@@ -598,8 +590,8 @@
|
|||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Önbellek boyutu",
|
"cache_size": "Önbellek boyutu",
|
||||||
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden)",
|
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden)",
|
||||||
"cache_ttl_min_override": "Minimum TTL'i değiştir",
|
"cache_ttl_min_override": "Minimum TTL'yi değiştir",
|
||||||
"cache_ttl_max_override": "Maksimum TTL'i değiştir",
|
"cache_ttl_max_override": "Maksimum TTL'yi değiştir",
|
||||||
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
|
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
|
||||||
"enter_cache_ttl_min_override": "Minimum TTL değerini girin (saniye)",
|
"enter_cache_ttl_min_override": "Minimum TTL değerini girin (saniye)",
|
||||||
"enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye)",
|
"enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye)",
|
||||||
|
|||||||
@@ -1,628 +0,0 @@
|
|||||||
{
|
|
||||||
"client_settings": "Налаштування клієнта",
|
|
||||||
"example_upstream_reserved": "Ви можете вказати DNS-сервер <0>для певних доменів</0>",
|
|
||||||
"example_upstream_comment": "Ви можете вказати коментар",
|
|
||||||
"upstream_parallel": "Використовувати паралельні запити, щоб пришвидшити вирішення одночасною чергою всіх оригінальних серверів.",
|
|
||||||
"parallel_requests": "Паралельні запити",
|
|
||||||
"load_balancing": "Балансування навантаження",
|
|
||||||
"load_balancing_desc": "Запитувати один сервер за раз. AdGuard Home використовуватиме зважений випадковий алгоритм для вибору сервера, щоб найшвидший сервер використовувався частіше.",
|
|
||||||
"bootstrap_dns": "Bootstrap DNS-сервери",
|
|
||||||
"bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для пошуку IP-адреси DoH/DoT серверів, які ви встановили.",
|
|
||||||
"local_ptr_title": "Приватні сервери для зворотного DNS",
|
|
||||||
"local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери, використовуючи rDNS, використовуються для отримання доменних імен клієнтів у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, буде використовуватись системний DNS-сервер.",
|
|
||||||
"local_ptr_default_resolver": "AdGuard Home усталено використовує такі зворотні DNS-резолвери: {{ip}}.",
|
|
||||||
"local_ptr_no_default_resolver": "AdGuard Home не зміг визначити приватні реверсивні DNS-резолвери, що були б придатними для цієї системи.",
|
|
||||||
"local_ptr_placeholder": "Вводьте одну адресу на рядок",
|
|
||||||
"resolve_clients_title": "Увімкнути запитування доменних імен для IP-адрес клієнтів",
|
|
||||||
"resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
|
|
||||||
"use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
|
|
||||||
"use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі по DHCP, /etc/hosts тощо.",
|
|
||||||
"check_dhcp_servers": "Перевірити DHCP-сервери",
|
|
||||||
"save_config": "Зберегти конфігурацію",
|
|
||||||
"enabled_dhcp": "DHCP-сервер увімкнено",
|
|
||||||
"disabled_dhcp": "DHCP-сервер вимкнено",
|
|
||||||
"unavailable_dhcp": "DHCP недоступний",
|
|
||||||
"unavailable_dhcp_desc": "AdGuard Home не може запустити DHCP-сервер у вашій ОС",
|
|
||||||
"dhcp_title": "DHCP-сервер (експериментальний!)",
|
|
||||||
"dhcp_description": "Якщо ваш роутер не пропонує налаштування DHCP, ви можете використати власний вбудований DHCP-сервер AdGuard.",
|
|
||||||
"dhcp_enable": "Увімкнути DHCP-сервер",
|
|
||||||
"dhcp_disable": "Вимкнути DHCP-сервер",
|
|
||||||
"dhcp_not_found": "Можна безпечно увімкнути вбудований DHCP-сервер — ми не знайшли жодного активного DHCP-сервера в мережі. Однак, ми радимо вам ще раз перевірити вручну, тому що наш автоматичний тест наразі не дає 100% гарантії.",
|
|
||||||
"dhcp_found": "Не знайдено DHCP-сервера в мережі. Вмикати вбудований DHCP-сервер небезпечно.",
|
|
||||||
"dhcp_leases": "Оренда DHCP",
|
|
||||||
"dhcp_static_leases": "Статичні оренди DHCP",
|
|
||||||
"dhcp_leases_not_found": "Оренду DHCP не знайдено",
|
|
||||||
"dhcp_config_saved": "Конфігурацію DHCP-сервера успішно збережено",
|
|
||||||
"dhcp_ipv4_settings": "Налаштування DHCP IPv4",
|
|
||||||
"dhcp_ipv6_settings": "Налаштування DHCP IPv6",
|
|
||||||
"form_error_required": "Обов'язкове поле",
|
|
||||||
"form_error_ip4_format": "Неправильна IPv4-адреса",
|
|
||||||
"form_error_ip4_range_start_format": "Неправильна IPv4-адреса для початку діапазону",
|
|
||||||
"form_error_ip4_range_end_format": "Неправильна IPv4-адреса для кінця діапазону",
|
|
||||||
"form_error_ip4_gateway_format": "Неправильна IPv4-адреса для шлюзу",
|
|
||||||
"form_error_ip6_format": "Неправильна IPv6-адреса",
|
|
||||||
"form_error_ip_format": "Неправильна IP-адреса",
|
|
||||||
"form_error_mac_format": "Неправильна MAC-адреса",
|
|
||||||
"form_error_client_id_format": "Неправильний ID клієнта",
|
|
||||||
"form_error_server_name": "Неправильна назва сервера",
|
|
||||||
"form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»",
|
|
||||||
"form_error_positive": "Повинно бути більше 0",
|
|
||||||
"form_error_negative": "Повинно дорівнювати 0 або більше",
|
|
||||||
"out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»",
|
|
||||||
"lower_range_start_error": "Має бути меншим за початкову адресу",
|
|
||||||
"greater_range_start_error": "Має бути більшим за початкову адресу",
|
|
||||||
"greater_range_end_error": "Має бути більшим за кінцеву адресу",
|
|
||||||
"subnet_error": "Адреси повинні бути в одній підмережі",
|
|
||||||
"gateway_or_subnet_invalid": "Неправильна маска підмережі",
|
|
||||||
"dhcp_form_gateway_input": "IP-адреса шлюзу",
|
|
||||||
"dhcp_form_subnet_input": "Маска підмережі",
|
|
||||||
"dhcp_form_range_title": "Діапазон IP-адрес",
|
|
||||||
"dhcp_form_range_start": "Початок діапазону",
|
|
||||||
"dhcp_form_range_end": "Кінець діапазону",
|
|
||||||
"dhcp_form_lease_title": "Час оренди DHCP (в секундах)",
|
|
||||||
"dhcp_form_lease_input": "Тривалість оренди",
|
|
||||||
"dhcp_interface_select": "Оберіть інтерфейс DHCP",
|
|
||||||
"dhcp_hardware_address": "Апаратна адреса",
|
|
||||||
"dhcp_ip_addresses": "IP-адреси",
|
|
||||||
"ip": "IP",
|
|
||||||
"dhcp_table_hostname": "Назва вузла",
|
|
||||||
"dhcp_table_expires": "Термін дії",
|
|
||||||
"dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!",
|
|
||||||
"dhcp_error": "Не вдалося визначити, чи є в мережі інший DHCP-сервер.",
|
|
||||||
"dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.",
|
|
||||||
"dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}</0>. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}</0>. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».",
|
|
||||||
"dhcp_lease_added": "Статичну оренду «{{key}}» успішно додано",
|
|
||||||
"dhcp_lease_deleted": "Статичну оренду «{{key}}» успішно видалено",
|
|
||||||
"dhcp_new_static_lease": "Нова статична оренда",
|
|
||||||
"dhcp_static_leases_not_found": "Не знайдено статичних оренд DHCP",
|
|
||||||
"dhcp_add_static_lease": "Додати статичну оренду",
|
|
||||||
"dhcp_reset_leases": "Скинути всі аренди",
|
|
||||||
"dhcp_reset_leases_confirm": "Ви дійсно хочете скинути усі аренди?",
|
|
||||||
"dhcp_reset_leases_success": "Аренди DHCP успішно скинуто",
|
|
||||||
"dhcp_reset": "Ви дійсно хочете скинути DHCP-конфігурацію?",
|
|
||||||
"country": "Країна",
|
|
||||||
"city": "Місто",
|
|
||||||
"delete_confirm": "Ви дійсно хочете видалити «{{key}}»?",
|
|
||||||
"form_enter_hostname": "Введіть назву вузла",
|
|
||||||
"error_details": "Подробиці помилки",
|
|
||||||
"response_details": "Деталі відповіді",
|
|
||||||
"request_details": "Деталі запиту",
|
|
||||||
"client_details": "Подробиці про клієнта",
|
|
||||||
"details": "Подробиці",
|
|
||||||
"back": "Назад",
|
|
||||||
"dashboard": "Панель керування",
|
|
||||||
"settings": "Налаштування",
|
|
||||||
"filters": "Фільтри",
|
|
||||||
"filter": "Фільтр",
|
|
||||||
"query_log": "Журнал запитів",
|
|
||||||
"compact": "Стисло",
|
|
||||||
"nothing_found": "Нічого не знайдено...",
|
|
||||||
"faq": "Часті питання",
|
|
||||||
"version": "Версія",
|
|
||||||
"address": "Адреса",
|
|
||||||
"protocol": "Протокол",
|
|
||||||
"on": "УВІМК",
|
|
||||||
"off": "ВИМК",
|
|
||||||
"copyright": "Авторське право",
|
|
||||||
"homepage": "Домівка",
|
|
||||||
"report_an_issue": "Повідомити про проблему",
|
|
||||||
"privacy_policy": "Політика приватності",
|
|
||||||
"enable_protection": "Увімкнути захист",
|
|
||||||
"enabled_protection": "Захист увімкнено",
|
|
||||||
"disable_protection": "Вимкнути захист",
|
|
||||||
"disabled_protection": "Захист вимкнено",
|
|
||||||
"refresh_statics": "Оновити статистику",
|
|
||||||
"dns_query": "DNS-запити",
|
|
||||||
"blocked_by": "<0>Заблоковано фільтрами</0>",
|
|
||||||
"stats_malware_phishing": "Заблоковано зловмисних/шахрайських програм",
|
|
||||||
"stats_adult": "Заблоковано вебсайтів для дорослих",
|
|
||||||
"stats_query_domain": "Найчастіші запити доменів",
|
|
||||||
"for_last_24_hours": "за останні 24 години",
|
|
||||||
"for_last_days": "за останній день",
|
|
||||||
"for_last_days_plural": "за останні {{count}} днів",
|
|
||||||
"stats_disabled": "Статистику вимкнено. Ви можете увімкнути її на <0>сторінці налаштувань</0>.",
|
|
||||||
"stats_disabled_short": "Статистику вимкнено",
|
|
||||||
"no_domains_found": "Доменів не знайдено",
|
|
||||||
"requests_count": "Кількість запитів",
|
|
||||||
"top_blocked_domains": "Найчастіше блоковані домени",
|
|
||||||
"top_clients": "Найактивніші клієнти",
|
|
||||||
"no_clients_found": "Клієнтів не знайдено",
|
|
||||||
"general_statistics": "Загальна статистика",
|
|
||||||
"number_of_dns_query_days": "Кількість DNS-запитів, оброблених за останні {{count}} дні",
|
|
||||||
"number_of_dns_query_days_plural": "Кількість DNS-запитів, оброблених за останні {{count}} днів",
|
|
||||||
"number_of_dns_query_24_hours": "Кількість DNS-запитів, оброблених за останні 24 години",
|
|
||||||
"number_of_dns_query_blocked_24_hours": "Кількість DNS-запитів, заблокованих фільтрами і списками блокування hosts",
|
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Кількість DNS-запитів, заблокованих модулем безпеки перегляду AdGuard",
|
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Кількість заблокованих вебсайтів для дорослих",
|
|
||||||
"enforced_save_search": "Примусовий безпечний пошук",
|
|
||||||
"number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
|
|
||||||
"average_processing_time": "Середній час обробки",
|
|
||||||
"average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
|
|
||||||
"block_domain_use_filters_and_hosts": "Блокувати домени з використанням фільтрів та hosts-файлів",
|
|
||||||
"filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
|
|
||||||
"use_adguard_browsing_sec": "Використовувати веб-службу безпечного перегляду AdGuard",
|
|
||||||
"use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи додано домен до списку веб-служби безпечного перегляду браузера. Він використовуватиме API для перевірки — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.",
|
|
||||||
"use_adguard_parental": "Використовувати вебсервіс Батьківського контролю AdGuard",
|
|
||||||
"use_adguard_parental_hint": "AdGuard Home перевірятиме, чи домен містить матеріали для дорослих. Він використовує той самий орієнтований на приватність API, що й веб-служба безпечного перегляду.",
|
|
||||||
"enforce_safe_search": "Використовувати безпечний пошук",
|
|
||||||
"enforce_save_search_hint": "AdGuard Home може примусово застосовувати безпечний пошук в таких пошукових системах: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
|
||||||
"no_servers_specified": "Не вказано сервери",
|
|
||||||
"general_settings": "Загальні налаштування",
|
|
||||||
"dns_settings": "Налаштування DNS",
|
|
||||||
"dns_blocklists": "Список блокування DNS",
|
|
||||||
"dns_allowlists": "Списки дозволів DNS",
|
|
||||||
"dns_blocklists_desc": "AdGuard Home блокуватиме домени зі списків блокування.",
|
|
||||||
"dns_allowlists_desc": "Домени зі списків дозволів DNS будуть дозволятися, навіть якщо вони знаходяться в будь-якому зі списків блокування.",
|
|
||||||
"custom_filtering_rules": "Власні правила фільтрування",
|
|
||||||
"encryption_settings": "Налаштування шифрування",
|
|
||||||
"dhcp_settings": "Налаштування DHCP",
|
|
||||||
"upstream_dns": "Upstream DNS-сервери",
|
|
||||||
"upstream_dns_help": "Введіть адреси серверів по одній на рядок. <a>Докладніше</a> про налаштування DNS-серверів.",
|
|
||||||
"upstream_dns_configured_in_file": "Налаштовано в {{path}}",
|
|
||||||
"test_upstream_btn": "Тест upstream серверів",
|
|
||||||
"upstreams": "Upstreams",
|
|
||||||
"apply_btn": "Застосувати",
|
|
||||||
"disabled_filtering_toast": "Фільтрування вимкнено",
|
|
||||||
"enabled_filtering_toast": "Фільтрування увімкнено",
|
|
||||||
"disabled_safe_browsing_toast": "Безпечний перегляд вимкнено",
|
|
||||||
"enabled_safe_browsing_toast": "Безпечний перегляд увімкнено",
|
|
||||||
"disabled_parental_toast": "Батьківський контроль вимкнено",
|
|
||||||
"enabled_parental_toast": "Батьківський контроль увімкнено",
|
|
||||||
"disabled_safe_search_toast": "Безпечний пошук вимкнено",
|
|
||||||
"enabled_save_search_toast": "Безпечний пошук увімкнено",
|
|
||||||
"enabled_table_header": "Увімкнено",
|
|
||||||
"name_table_header": "Назва",
|
|
||||||
"list_url_table_header": "URL списку",
|
|
||||||
"rules_count_table_header": "Кількість правил",
|
|
||||||
"last_time_updated_table_header": "Востаннє оновлено",
|
|
||||||
"actions_table_header": "Дії",
|
|
||||||
"request_table_header": "Запит",
|
|
||||||
"edit_table_action": "Редагувати",
|
|
||||||
"delete_table_action": "Видалити",
|
|
||||||
"elapsed": "Витрачений час",
|
|
||||||
"filters_and_hosts_hint": "AdGuard Home розуміє основні правила блокування і синтаксис файлів hosts.",
|
|
||||||
"no_blocklist_added": "Списків блокування не додано",
|
|
||||||
"no_whitelist_added": "Списків дозволів не додано",
|
|
||||||
"add_blocklist": "Додати список блокування",
|
|
||||||
"add_allowlist": "Додати список дозволів",
|
|
||||||
"cancel_btn": "Скасувати",
|
|
||||||
"enter_name_hint": "Введіть назву",
|
|
||||||
"enter_url_or_path_hint": "Уведіть URL-адресу чи абсолютний шлях до списку",
|
|
||||||
"check_updates_btn": "Перевірити оновлення",
|
|
||||||
"new_blocklist": "Новий список блокування",
|
|
||||||
"new_allowlist": "Новий список дозволів",
|
|
||||||
"edit_blocklist": "Змінити список блокування",
|
|
||||||
"edit_allowlist": "Змінити список дозволів",
|
|
||||||
"choose_blocklist": "Виберіть списки блокування",
|
|
||||||
"choose_allowlist": "Обрати списки дозволених сайтів",
|
|
||||||
"enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.",
|
|
||||||
"enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.",
|
|
||||||
"form_error_url_format": "Неправильний формат URL",
|
|
||||||
"form_error_url_or_path_format": "Помилкова URL-адреса чи абсолютний шлях до списку",
|
|
||||||
"custom_filter_rules": "Власні правила фільтрування",
|
|
||||||
"custom_filter_rules_hint": "Вводьте одне правило на рядок. Ви можете використовувати правила блокування чи синтаксис файлів hosts.",
|
|
||||||
"examples_title": "Зразки",
|
|
||||||
"example_meaning_filter_block": "блокує доступ до домену example.org та всіх його піддоменів",
|
|
||||||
"example_meaning_filter_whitelist": "розблоковує доступ до домену example.org та всіх його піддоменів",
|
|
||||||
"example_meaning_host_block": "AdGuard Home повертатиме адресу 127.0.0.1 для домену example.org (але не його піддоменів).",
|
|
||||||
"example_comment": "! Так можна додавати коментар",
|
|
||||||
"example_comment_meaning": "просто коментар",
|
|
||||||
"example_comment_hash": "# Це також коментар",
|
|
||||||
"example_regex_meaning": "блокує доступ до доменів, що відповідають вказаному звичайному виразу",
|
|
||||||
"example_upstream_regular": "звичайний DNS (через UDP)",
|
|
||||||
"example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>",
|
|
||||||
"example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>",
|
|
||||||
"example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0>",
|
|
||||||
"example_upstream_sdns": "ви можете використовувати <0>DNS Stamps</0> для вирішення <1>DNSCrypt</1> або <2>DNS-over-HTTPS</2>",
|
|
||||||
"example_upstream_tcp": "звичайний DNS (через TCP)",
|
|
||||||
"all_lists_up_to_date_toast": "Всі списки вже оновлені",
|
|
||||||
"updated_upstream_dns_toast": "DNS-сервери оновлено",
|
|
||||||
"dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
|
|
||||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
|
|
||||||
"unblock": "Дозволити",
|
|
||||||
"block": "Заборонити",
|
|
||||||
"disallow_this_client": "Заборонити цього клієнта",
|
|
||||||
"allow_this_client": "Дозволити цей клієнт",
|
|
||||||
"block_for_this_client_only": "Заборонити тільки цей клієнт",
|
|
||||||
"unblock_for_this_client_only": "Дозволити тільки цей клієнт",
|
|
||||||
"time_table_header": "Час",
|
|
||||||
"date": "Дата",
|
|
||||||
"domain_name_table_header": "Назва домену",
|
|
||||||
"domain_or_client": "Домен чи клієнт",
|
|
||||||
"type_table_header": "Тип",
|
|
||||||
"response_table_header": "Відповідь",
|
|
||||||
"response_code": "Код відповіді",
|
|
||||||
"client_table_header": "Клієнт",
|
|
||||||
"empty_response_status": "Порожньо",
|
|
||||||
"show_all_filter_type": "Показати все",
|
|
||||||
"show_filtered_type": "Показати фільтровані",
|
|
||||||
"no_logs_found": "Немає записів",
|
|
||||||
"refresh_btn": "Оновити",
|
|
||||||
"previous_btn": "Назад",
|
|
||||||
"next_btn": "Далі",
|
|
||||||
"loading_table_status": "Завантаження...",
|
|
||||||
"page_table_footer_text": "Сторінка",
|
|
||||||
"rows_table_footer_text": "рядків",
|
|
||||||
"updated_custom_filtering_toast": "Власні правила фільтрування збережено",
|
|
||||||
"rule_removed_from_custom_filtering_toast": "Правило вилучено з власних правил фільтрування: {{rule}}",
|
|
||||||
"rule_added_to_custom_filtering_toast": "Правило додано до власних правил фільтрування: {{rule}}",
|
|
||||||
"query_log_response_status": "Стан: {{value}}",
|
|
||||||
"query_log_filtered": "Фільтровано з {{filter}}",
|
|
||||||
"query_log_confirm_clear": "Ви впевнені, що хочете цілком очистити журнал запитів?",
|
|
||||||
"query_log_cleared": "Журнал запитів успішно очищено",
|
|
||||||
"query_log_updated": "Журнал запитів успішно оновлено",
|
|
||||||
"query_log_clear": "Очистити журнал запитів",
|
|
||||||
"query_log_retention": "Час зберігання журналу",
|
|
||||||
"query_log_enable": "Увімкнути журнал",
|
|
||||||
"query_log_configuration": "Конфігурація журналу",
|
|
||||||
"query_log_disabled": "Журнал запитів вимкнений. Конфігурацію можна змінити в <0>налаштуваннях</0>",
|
|
||||||
"query_log_strict_search": "Використовуйте подвійні лапки для точного пошуку",
|
|
||||||
"query_log_retention_confirm": "Ви дійсно хочете змінити час зберігання журналу? Якщо ви зменшите значення, деякі дані будуть втрачені",
|
|
||||||
"anonymize_client_ip": "Анонімізація IP-адреси клієнта",
|
|
||||||
"anonymize_client_ip_desc": "Не зберігайте повну IP-адресу клієнта в журналах і статистиці",
|
|
||||||
"dns_config": "Конфігурація DNS-сервера",
|
|
||||||
"dns_cache_config": "Конфігурація кешу DNS",
|
|
||||||
"dns_cache_config_desc": "Тут ви можете налаштувати кеш DNS",
|
|
||||||
"blocking_mode": "Режим блокування",
|
|
||||||
"default": "Типовий",
|
|
||||||
"nxdomain": "NXDOMAIN",
|
|
||||||
"refused": "REFUSED",
|
|
||||||
"null_ip": "Нульовий IP",
|
|
||||||
"custom_ip": "Власний IP",
|
|
||||||
"blocking_ipv4": "Блокування IPv4",
|
|
||||||
"blocking_ipv6": "Блокування IPv6",
|
|
||||||
"dnscrypt": "DNSCrypt",
|
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
|
||||||
"dns_over_quic": "DNS-over-QUIC",
|
|
||||||
"client_id": "Ідентифікатор клієнта",
|
|
||||||
"client_id_placeholder": "Введіть ідентифікатор клієнта",
|
|
||||||
"client_id_desc": "Різні клієнти можуть бути розпізнані завдяки спеціальному ідентифікатору. <a>Докладніше про ідентифікацію клієнтів</a>.",
|
|
||||||
"download_mobileconfig_doh": "Завантажити .mobileconfig для DNS-over-HTTPS",
|
|
||||||
"download_mobileconfig_dot": "Завантажити .mobileconfig для DNS-over-TLS",
|
|
||||||
"download_mobileconfig": "Завантажити файл конфігурації",
|
|
||||||
"plain_dns": "Звичайний DNS",
|
|
||||||
"form_enter_rate_limit": "Уведіть обмеження швидкості",
|
|
||||||
"rate_limit": "Обмеження швидкості",
|
|
||||||
"edns_enable": "Увімкнути відправку EDNS Client Subnet",
|
|
||||||
"edns_cs_desc": "Надсилати підмережі клієнтів на DNS-сервери.",
|
|
||||||
"rate_limit_desc": "Кількість запитів в секунду, які може робити один клієнт. Встановлене значення «0» означатиме необмежену кількість.",
|
|
||||||
"blocking_ipv4_desc": "IP-адреса, яку потрібно видати для заблокованого A запиту",
|
|
||||||
"blocking_ipv6_desc": "IP-адреса, яку потрібно видати для заблокованого АААА запиту",
|
|
||||||
"blocking_mode_default": "Усталено: відповідь із нульовою IP-адресою (0.0.0.0 для A; :: для AAAA), якщо заблоковано правилом у Adblock-стилі; відповідь зазначеною у правилі IP-адресою, якщо заблокувано правилом у hosts-стилі",
|
|
||||||
"blocking_mode_refused": "ВІДМОВЛЕНО: Відповісти з кодом ВІДМОВЛЕНО",
|
|
||||||
"blocking_mode_nxdomain": "NXDOMAIN: Відповісти з кодом NXDOMAIN",
|
|
||||||
"blocking_mode_null_ip": "Нульовий IP: Відповісти з нульовою IP-адресою (0.0.0.0 для A; :: для AAAA)",
|
|
||||||
"blocking_mode_custom_ip": "Спеціальна IP-адреса: Відповісти із вручну встановленою IP-адресою",
|
|
||||||
"upstream_dns_client_desc": "Якщо це поле залишатиметься порожнім, AdGuard Home використовуватиме сервери, вказані в <0>налаштуваннях DNS</0>.",
|
|
||||||
"tracker_source": "Джерело відстежувача",
|
|
||||||
"source_label": "Джерело",
|
|
||||||
"found_in_known_domain_db": "Знайдений у базі даних відомих доменів.",
|
|
||||||
"category_label": "Категорія",
|
|
||||||
"rule_label": "Правило(-а)",
|
|
||||||
"list_label": "Список",
|
|
||||||
"unknown_filter": "Невідомий фільтр {{filterId}}",
|
|
||||||
"known_tracker": "Відомі трекери",
|
|
||||||
"install_welcome_title": "Вітаємо в AdGuard Home!",
|
|
||||||
"install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.",
|
|
||||||
"install_settings_title": "Веб-інтерфейс адміністратора",
|
|
||||||
"install_settings_listen": "Мережевий інтерфейс",
|
|
||||||
"install_settings_port": "Порт",
|
|
||||||
"install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
|
|
||||||
"form_error_port": "Уведіть правильне значення порту",
|
|
||||||
"install_settings_dns": "DNS-сервер",
|
|
||||||
"install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
|
|
||||||
"install_settings_all_interfaces": "Усі інтерфейси",
|
|
||||||
"install_auth_title": "Авторизація",
|
|
||||||
"install_auth_desc": "Необходно налаштувати автентифікацію паролем для вебінтерфейсу AdGuard Home. Навіть якщо він доступний лише у вашій локальній мережі, важливо захистити його від необмеженого доступу.\n\nДолжна быть настроена аутентификация паролем для веб-интерфейса AdGuard Home. Даже если он доступен только в вашей локальной сети, важно защитить его от неограниченного доступа.",
|
|
||||||
"install_auth_username": "Ім'я користувача",
|
|
||||||
"install_auth_password": "Пароль",
|
|
||||||
"install_auth_confirm": "Підтвердьте пароль",
|
|
||||||
"install_auth_username_enter": "Уведіть ім'я користувача",
|
|
||||||
"install_auth_password_enter": "Введіть пароль",
|
|
||||||
"install_step": "Крок",
|
|
||||||
"install_devices_title": "Налаштуйте ваші пристрої",
|
|
||||||
"install_devices_desc": "Щоби розпочати використовувати AdGuard Home, вам потрібно налаштувати ваші пристої для його використання.",
|
|
||||||
"install_submit_title": "Вітаємо!",
|
|
||||||
"install_submit_desc": "Процедура налаштування завершена і тепер все готово, аби почати користуватися AdGuard Home.",
|
|
||||||
"install_devices_router": "Роутер",
|
|
||||||
"install_devices_router_desc": "Це налаштування буде автоматично охоплювати всі пристрої, що під'єднано до домашнього маршрутизатора. Вам не потрібно буде налаштовувати кожен з них вручну.",
|
|
||||||
"install_devices_address": "DNS-сервер AdGuard Home прослуховує наступні адреси",
|
|
||||||
"install_devices_router_list_1": "Відкрийте налаштування маршрутизатора. Зазвичай ви можете отримати до нього доступ із браузера за допомогою URL-адреси, наприклад, http://192.168.0.1/ або http://192.168.1.1/. Можливо, треба буде ввести пароль. Якщо ви його не знаєте, часто можна скинути пароль, натиснувши кнопку на самому маршрутизаторі. Для деяких маршрутизаторів потрібна спеціальна програма, яка в такому випадку повинна бути вже встановлена на вашому комп’ютері чи телефоні.",
|
|
||||||
"install_devices_router_list_2": "Знайдіть налаштування DHCP/DNS. Шукайте літери DNS поруч із полем, в яке можна ввести два або три набори чисел, кожен з яких розбитий на чотири групи від однієї до трьох цифр.",
|
|
||||||
"install_devices_router_list_3": "Введіть туди адреси вашого домашнього сервера AdGuard.",
|
|
||||||
"install_devices_router_list_4": "Ви не можете встановити власний DNS-сервер на деяких типах маршрутизаторів. У цьому разі вам може допомогти налаштування AdGuard Home в якості <0>DHCP-сервера</0>. В іншому разі вам потрібно знайти інструкцію щодо налаштування DNS-сервера для вашої конкретної моделі маршрутизатора.",
|
|
||||||
"install_devices_windows_list_1": "Відкрийте Панель керування через меню «Пуск» або пошук Windows.",
|
|
||||||
"install_devices_windows_list_2": "Перейдіть до категорії Мережа й Інтернет, а потім до Центру мереж і спільного доступу.",
|
|
||||||
"install_devices_windows_list_3": "У лівій частині екрана знайдіть текст «Змінити настройки адаптера» та натисніть на нього.",
|
|
||||||
"install_devices_windows_list_4": "Виберіть своє активне з'єднання, клацніть на ньому правою кнопкою миші та виберіть Властивості.",
|
|
||||||
"install_devices_windows_list_5": "Знайдіть у списку пункт «Internet Protocol Version 4 (TCP/IPv4)» або «Internet Protocol Version 6 (TCP/IPv6)», виберіть його та натисніть кнопку Властивості ще раз.",
|
|
||||||
"install_devices_windows_list_6": "Виберіть «Використовувати наступні адреси DNS-серверів» та введіть адреси вашого сервера AdGuard Home.",
|
|
||||||
"install_devices_macos_list_1": "Клацніть на піктограму Apple і перейдіть до Системних налаштувань.",
|
|
||||||
"install_devices_macos_list_2": "Клацніть на Мережа.",
|
|
||||||
"install_devices_macos_list_3": "Виберіть перше з'єднання зі списку та натисніть кнопку Додатково.",
|
|
||||||
"install_devices_macos_list_4": "Виберіть вкладку DNS і введіть адреси сервера AdGuard Home.",
|
|
||||||
"install_devices_android_list_1": "На головному екрані меню Android торкніться Налаштування.",
|
|
||||||
"install_devices_android_list_2": "У меню торкніться Wi-Fi. З'явиться екран із переліком усіх доступних мереж (неможливо встановити власний DNS для мобільного з'єднання).",
|
|
||||||
"install_devices_android_list_3": "Довго натисніть на мережу, до якої ви приєднані, та торкніться «Змінити мережу».",
|
|
||||||
"install_devices_android_list_4": "На деяких пристроях вам може знадобитися встановити прапорець Додатково, щоб побачити подальші налаштування. Щоб відредагувати налаштування DNS для Android, вам потрібно буде переключити налаштування IP з DHCP на статичні.",
|
|
||||||
"install_devices_android_list_5": "Змініть встановлені значення DNS 1 і DNS 2 на адреси вашого домашнього сервера AdGuard.",
|
|
||||||
"install_devices_ios_list_1": "На головному екрані торкніться Налаштування.",
|
|
||||||
"install_devices_ios_list_2": "Виберіть Wi-Fi у меню ліворуч (неможливо налаштувати DNS для мобільних мереж).",
|
|
||||||
"install_devices_ios_list_3": "Натисніть на назву поточно активної мережі.",
|
|
||||||
"install_devices_ios_list_4": "У полі DNS введіть адреси вашого сервера AdGuard Home.",
|
|
||||||
"get_started": "Розпочати",
|
|
||||||
"next": "Наступні",
|
|
||||||
"open_dashboard": "Відкрити інформаційну панель",
|
|
||||||
"install_saved": "Збережено успішно",
|
|
||||||
"encryption_title": "Шифрування",
|
|
||||||
"encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS так і для веб-інтерфейсу адміністратора",
|
|
||||||
"encryption_config_saved": "Конфігурацію шифрування збережено",
|
|
||||||
"encryption_server": "Назва сервера",
|
|
||||||
"encryption_server_enter": "Введіть ваше доменне ім'я",
|
|
||||||
"encryption_server_desc": "Для використання HTTPS вам потрібно ввести назву сервера, який відповідає вашому SSL-сертифікату або сертифікату з підтримкою піддоменів. Якщо значення не вказано, то сервер буде приймати TLS-з'єднання для будь-якого домену.",
|
|
||||||
"encryption_redirect": "Автоматично перенаправляти на HTTPS",
|
|
||||||
"encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.",
|
|
||||||
"encryption_https": "Порт HTTPS",
|
|
||||||
"encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.",
|
|
||||||
"encryption_dot": "Порт DNS-over-TLS",
|
|
||||||
"encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.",
|
|
||||||
"encryption_doq": "Порт DNS-over-QUIC",
|
|
||||||
"encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-QUIC. Це експериментально і може бути ненадійним. Крім того, зараз не так багато клієнтів, які це підтримують.",
|
|
||||||
"encryption_certificates": "Сертифікати",
|
|
||||||
"encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безкоштовний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
|
|
||||||
"encryption_certificates_input": "Скопіюйте/вставте сюди свої кодовані PEM сертифікати.",
|
|
||||||
"encryption_status": "Статус",
|
|
||||||
"encryption_expire": "Закічнується",
|
|
||||||
"encryption_key": "Приватний ключ",
|
|
||||||
"encryption_key_input": "Скопіюйте/вставте сюди свій приватний ключ кодований PEM для вашого сертифіката.",
|
|
||||||
"encryption_enable": "Увімкнути шифрування (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
|
|
||||||
"encryption_enable_desc": "Якщо ввімкнено шифрування, інтерфейс адміністратора AdGuard Home буде працювати через HTTPS, а DNS-сервер буде прослуховувати запити через DNS-over-HTTPS і DNS-over-TLS.",
|
|
||||||
"encryption_chain_valid": "Ланцюжок сертифікатів дійсний",
|
|
||||||
"encryption_chain_invalid": "Ланцюжок сертифікатів не дійсний",
|
|
||||||
"encryption_key_valid": "Це дійсний приватний ключ {{type}}",
|
|
||||||
"encryption_key_invalid": "Це недійсний приватний ключ {{type}}",
|
|
||||||
"encryption_subject": "Обє'кт",
|
|
||||||
"encryption_issuer": "Видавець",
|
|
||||||
"encryption_hostnames": "Назви вузлів",
|
|
||||||
"encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
|
|
||||||
"topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
|
|
||||||
"topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
|
|
||||||
"form_error_port_range": "Введіть значення порту в діапазоні 80−65535",
|
|
||||||
"form_error_port_unsafe": "Це небезпечний порт",
|
|
||||||
"form_error_equal": "Мають бути різні значення",
|
|
||||||
"form_error_password": "Пароль не співпадає",
|
|
||||||
"reset_settings": "Скинути налаштування",
|
|
||||||
"update_announcement": "AdGuard Home {{version}} тепер доступний! <0>Докладніше</0>.",
|
|
||||||
"setup_guide": "Посібник з налаштування",
|
|
||||||
"dns_addresses": "DNS-адреси",
|
|
||||||
"dns_start": "DNS-сервер запускається",
|
|
||||||
"dns_status_error": "Помилка перевірки стану сервера DNS",
|
|
||||||
"down": "Недоступний",
|
|
||||||
"fix": "Виправити",
|
|
||||||
"dns_providers": "<0>Список відомих DNS-провайдерів</0> на вибір.",
|
|
||||||
"update_now": "Оновити зараз",
|
|
||||||
"update_failed": "Помилка автоматичного оновлення. Будь ласка, <a>виконайте ці кроки</a> аби оновити вручну.",
|
|
||||||
"processing_update": "Зачекайте будь ласка, AdGuard Home оновлюється",
|
|
||||||
"clients_title": "Клієнти",
|
|
||||||
"clients_desc": "Налаштуйте пристрої, під'єднані до AdGuard Home",
|
|
||||||
"settings_global": "Загальні",
|
|
||||||
"settings_custom": "Власні",
|
|
||||||
"table_client": "Клієнт",
|
|
||||||
"table_name": "Назва",
|
|
||||||
"save_btn": "Зберегти",
|
|
||||||
"client_add": "Додати Клієнта",
|
|
||||||
"client_new": "Новий Клієнт",
|
|
||||||
"client_edit": "Редагувати Клієнта",
|
|
||||||
"client_identifier": "Ідентифікатор",
|
|
||||||
"ip_address": "IP-адреса",
|
|
||||||
"client_identifier_desc": "Клієнтів можна ідентифікувати за IP-, CIDR-, MAC-адресами або ж за спеціальним клієнтським ідентифікатором (можливий для DoT, DoH та DoQ). <0>Докладніше про ідентифікацію клієнтів</0>.",
|
|
||||||
"form_enter_ip": "Введіть IP",
|
|
||||||
"form_enter_subnet_ip": "Введіть IP-адресу в підмережі «{{cidr}}»",
|
|
||||||
"form_enter_mac": "Введіть MAC",
|
|
||||||
"form_enter_id": "Введіть ідентифікатор",
|
|
||||||
"form_add_id": "Додати ідентифікатор",
|
|
||||||
"form_client_name": "Введіть ім'я клієнта",
|
|
||||||
"name": "Ім'я",
|
|
||||||
"client_global_settings": "Використати загальні налаштування",
|
|
||||||
"client_deleted": "Клієнта «{{key}}» успішно видалено",
|
|
||||||
"client_added": "Клієнта «{{key}}» успішно додано",
|
|
||||||
"client_updated": "Клієнта «{{key}}» успішно оновлено",
|
|
||||||
"clients_not_found": "Клієнтів не знайдено",
|
|
||||||
"client_confirm_delete": "Ви впевнені, що хочете видалити клієнта «{{key}}»?",
|
|
||||||
"list_confirm_delete": "Ви впевнені, що хочете видалити цей список?",
|
|
||||||
"auto_clients_title": "Клієнти (поза налаштуванням)",
|
|
||||||
"auto_clients_desc": "Дані про клієнтів, які використовують AdGuard Home, але не зберігаються в конфігурації",
|
|
||||||
"access_title": "Налаштування доступу",
|
|
||||||
"access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home.",
|
|
||||||
"access_allowed_title": "Дозволені клієнти",
|
|
||||||
"access_allowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home прийматиме запити лише від цих клієнтів.",
|
|
||||||
"access_disallowed_title": "Заборонені клієнти",
|
|
||||||
"access_disallowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список дозволених клієнтів, то це поле проігнорується.",
|
|
||||||
"access_blocked_title": "Заборонені домени",
|
|
||||||
"access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.",
|
|
||||||
"access_settings_saved": "Налаштування доступу успішно збережено",
|
|
||||||
"updates_checked": "Оновлення успішно перевірені",
|
|
||||||
"updates_version_equal": "AdGuard Home останньої версії",
|
|
||||||
"check_updates_now": "Перевірити наявність оновлень",
|
|
||||||
"dns_privacy": "Конфіденційність DNS",
|
|
||||||
"setup_dns_privacy_1": "<0>DNS-over-TLS: </0>Використайте рядок <1>{{address}}</1>.",
|
|
||||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Використайте рядок <1>{{address}}</1>.",
|
|
||||||
"setup_dns_privacy_3": "<0>Ось перелік програмного забезпечення, яке можете використати.</0>",
|
|
||||||
"setup_dns_privacy_4": "На пристрої iOS 14 або macOS Big Sur ви можете завантажити спеціальний файл .mobileconfig, який додасть до налаштувань DNS сервери <highlight>DNS-over-HTTPS</highlight> або <highlight>DNS-over-TLS</highlight>.",
|
|
||||||
"setup_dns_privacy_android_1": "Android 9 підтримує DNS-over-TLS. Щоб його налаштувати, перейдіть у Налаштування → Мережа та Інтернет → Додатково → Приватний DNS і введіть там свій домен.",
|
|
||||||
"setup_dns_privacy_android_2": "<0>AdGuard для Android</0> підтримує <1>DNS-over-HTTPS</1> і <1>DNS-over-TLS</1>.",
|
|
||||||
"setup_dns_privacy_android_3": "<0>Intra</0> додає підтримку <1>DNS-over-HTTPS</1> для Android.",
|
|
||||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> підтримує <1>DNS-over-HTTPS</1>, але для того, щоб налаштувати його на використання власного сервера, вам потрібно буде створити для нього <2>штамп DNS</2>.",
|
|
||||||
"setup_dns_privacy_ios_2": "<0>AdGuard для iOS</0> підтримує налаштування <1>DNS over-HTTPS</1> і <1>DNS over over TLS</1>.",
|
|
||||||
"setup_dns_privacy_other_title": "Інші реалізації",
|
|
||||||
"setup_dns_privacy_other_1": "Сам AdGuard Home може слугувати захищеним клієнтом DNS на будь-якій платформі.",
|
|
||||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> підтримує всі відомі захищені протоколи DNS.",
|
|
||||||
"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_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.",
|
|
||||||
"rewrite_added": "Перезапис DNS для «{{key}}» успішно додано",
|
|
||||||
"rewrite_deleted": "Перезапис DNS для «{{key}}» успішно видалено",
|
|
||||||
"rewrite_add": "Додати перезапис DNS",
|
|
||||||
"rewrite_not_found": "Перезаписів DNS не знайдено",
|
|
||||||
"rewrite_confirm_delete": "Ви впевнені, що хочете видалити перезапис DNS для «{{key}}»?",
|
|
||||||
"rewrite_desc": "Дозволяє легко налаштувати власну відповідь DNS для певного доменного імені.",
|
|
||||||
"rewrite_applied": "Застосовано правило перезапису",
|
|
||||||
"rewrite_hosts_applied": "Перезаписано правилом hosts-файлу",
|
|
||||||
"dns_rewrites": "DNS перезаписи",
|
|
||||||
"form_domain": "Введіть доменне ім’я або підстановний знак",
|
|
||||||
"form_answer": "Введіть IP-адресу або доменне ім'я",
|
|
||||||
"form_error_domain_format": "Неправильний формат домену",
|
|
||||||
"form_error_answer_format": "Неправильний формат відопвіді",
|
|
||||||
"configure": "Налаштувати",
|
|
||||||
"main_settings": "Головні налаштування",
|
|
||||||
"block_services": "Блокувати конкретні сервіси",
|
|
||||||
"blocked_services": "Заблоковані сервіси",
|
|
||||||
"blocked_services_desc": "Дозволяє швидко блокувати популярні сайти та сервіси.",
|
|
||||||
"blocked_services_saved": "Заблоковані сервіси успішно збережено",
|
|
||||||
"blocked_services_global": "Використовувати глобально заблоковані сервіси",
|
|
||||||
"blocked_service": "Заблокований сервіс",
|
|
||||||
"block_all": "Блокувати все",
|
|
||||||
"unblock_all": "Розблокувати все",
|
|
||||||
"encryption_certificate_path": "Шлях до сертифіката",
|
|
||||||
"encryption_private_key_path": "Шлях до приватного ключа",
|
|
||||||
"encryption_certificates_source_path": "Вказати шлях до сертифікату",
|
|
||||||
"encryption_certificates_source_content": "Вставити вміст сертифікату",
|
|
||||||
"encryption_key_source_path": "Вказати приватний ключ",
|
|
||||||
"encryption_key_source_content": "Вставити вміст приватного ключа",
|
|
||||||
"stats_params": "Налаштування статистики",
|
|
||||||
"config_successfully_saved": "Конфігурацію успішно збережено",
|
|
||||||
"interval_6_hour": "6 годин",
|
|
||||||
"interval_24_hour": "24 години",
|
|
||||||
"interval_days": "{{count}} день",
|
|
||||||
"interval_days_plural": "{{count}} дні(в)",
|
|
||||||
"domain": "Домен",
|
|
||||||
"punycode": "Punycode",
|
|
||||||
"answer": "Відповідь",
|
|
||||||
"filter_added_successfully": "Фільтр успішно додано",
|
|
||||||
"filter_removed_successfully": "Фільтр успішно видалено",
|
|
||||||
"filter_updated": "Фільтр успішно оновлено",
|
|
||||||
"statistics_configuration": "Налаштування статистики",
|
|
||||||
"statistics_retention": "Збереження статистики",
|
|
||||||
"statistics_retention_desc": "Якщо зменшити значення інтервалу, деякі дані будуть втрачені",
|
|
||||||
"statistics_clear": "Очистити статистику",
|
|
||||||
"statistics_clear_confirm": "Ви впевнені, що хочете очистити статистику?",
|
|
||||||
"statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені",
|
|
||||||
"statistics_cleared": "Статистика успішно очищена",
|
|
||||||
"statistics_enable": "Увімкнути статистику",
|
|
||||||
"interval_hours": "{{count}} година",
|
|
||||||
"interval_hours_plural": "{{count}} годин(и)",
|
|
||||||
"filters_configuration": "Конфігурація фільтрів",
|
|
||||||
"filters_enable": "Увімкнути фільтри",
|
|
||||||
"filters_interval": "Інтервал оновлення фільтрів",
|
|
||||||
"disabled": "Вимкнено",
|
|
||||||
"username_label": "Ім'я користувача",
|
|
||||||
"username_placeholder": "Уведіть ім'я користувача",
|
|
||||||
"password_label": "Пароль",
|
|
||||||
"password_placeholder": "Введіть пароль",
|
|
||||||
"sign_in": "Увійти",
|
|
||||||
"sign_out": "Вийти",
|
|
||||||
"forgot_password": "Забули пароль?",
|
|
||||||
"forgot_password_desc": "Виконайте <0>ці кроки</0>, щоб створити новий пароль для свого імені користувача.",
|
|
||||||
"location": "Місцезнаходження",
|
|
||||||
"orgname": "Назва організації",
|
|
||||||
"netname": "Назва мережі",
|
|
||||||
"network": "Мережа",
|
|
||||||
"descr": "Опис",
|
|
||||||
"whois": "Whois",
|
|
||||||
"filtering_rules_learn_more": "<0>Як створити власні списки блокування</0>.",
|
|
||||||
"blocked_by_response": "У відповідь заблоковано по CNAME або IP",
|
|
||||||
"blocked_by_cname_or_ip": "Заблоковано по CNAME або IP",
|
|
||||||
"try_again": "Спробувати знову",
|
|
||||||
"domain_desc": "Введіть доменне ім’я або підстановний знак, який потрібно переписати.",
|
|
||||||
"example_rewrite_domain": "перепишіть відповіді лише для цього доменного імені.",
|
|
||||||
"example_rewrite_wildcard": "перепишіть відповіді для всіх субдоменів <0>example.org</0>.",
|
|
||||||
"rewrite_ip_address": "IP-адреса: використайте цю IP-адресу у відповіді A або AAAA",
|
|
||||||
"rewrite_domain_name": "Доменне ім’я: додайте запис CNAME",
|
|
||||||
"rewrite_A": "<0>A</0>: спеціальне значення, зберігайте <0>A</0> записи із вищого сервера",
|
|
||||||
"rewrite_AAAA": "<0>AAAA</0>: спеціальне значення, зберігайте <0>AAAA</0> записи із вищого сервера",
|
|
||||||
"disable_ipv6": "Вимкнути вирішення IPv6-адрес",
|
|
||||||
"disable_ipv6_desc": "Ігнорувати DNS-запити для IPv6-адрес (тип AAAA).",
|
|
||||||
"fastest_addr": "Найшвидша IP-адреса",
|
|
||||||
"fastest_addr_desc": "Опитати всі DNS-сервери й повернути найшвидшу IP-адресу серед усіх наданих. Це сповільнить швидкість DNS-запитів, оскільки AdGuard Home повинен буде чекати відповіді усіх DNS-серверів, але водночас може покращити якість з'єднання.",
|
|
||||||
"autofix_warning_text": "Якщо ви натиснете «Виправити», AdGuard Home налаштує вашу систему на використання DNS-сервера AdGuard Home.",
|
|
||||||
"autofix_warning_list": "Це виконає наступні завдання: <0>Деактивує систему DNSStubListener</0> <0>Змінить адресу DNS сервера на 127.0.0.1</0> <0>Замінить символічне посилання /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Зупинить DNSStubListener (перезапустить сервіс systemd-resolved)</0>",
|
|
||||||
"autofix_warning_result": "В результаті буде усталено, що усі DNS-запити вашої системи будуть опрацьовані AdGuard Home.",
|
|
||||||
"tags_title": "Теги",
|
|
||||||
"tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше</0>",
|
|
||||||
"form_select_tags": "Виберіть теги клієнта",
|
|
||||||
"check_title": "Перевірте фільтрування",
|
|
||||||
"check_desc": "Перевірте чи фільтрується назва вузла",
|
|
||||||
"check": "Перевірити",
|
|
||||||
"form_enter_host": "Введіть назву вузла",
|
|
||||||
"filtered_custom_rules": "Відфільтровано за власними правилами фільтрування",
|
|
||||||
"choose_from_list": "Виберіть зі списку",
|
|
||||||
"add_custom_list": "Додати власний список",
|
|
||||||
"host_whitelisted": "Вузол додано до списку дозволів",
|
|
||||||
"check_ip": "IP адреси: {{ip}}",
|
|
||||||
"check_cname": "CNAME: {{cname}}",
|
|
||||||
"check_reason": "Причина: {{reason}}",
|
|
||||||
"check_service": "Назва сервісу: {{service}}",
|
|
||||||
"service_name": "Назва сервісу",
|
|
||||||
"check_not_found": "Не знайдено у ваших списках фільтрів",
|
|
||||||
"client_confirm_block": "Ви впевнені, що хочете заблокувати клієнта «{{ip}}»?",
|
|
||||||
"client_confirm_unblock": "Ви впевнені, що хочете розблокувати клієнт «{{ip}}»?",
|
|
||||||
"client_blocked": "Клієнта «{{ip}}» успішно заблоковано",
|
|
||||||
"client_unblocked": "Клієнта «{{ip}}» успішно розблоковано",
|
|
||||||
"static_ip": "Статична IP-адреса",
|
|
||||||
"static_ip_desc": "AdGuard Home - це сервер, тому йому потрібна статична IP-адреса для нормальної роботи. В іншому випадку, в певний момент, ваш маршрутизатор може призначити іншу IP-адресу цьому пристрою.",
|
|
||||||
"set_static_ip": "Встановити статичну IP-адресу",
|
|
||||||
"install_static_ok": "Гарні новини! Статична IP-адреса вже налаштована",
|
|
||||||
"install_static_error": "AdGuard Home не може налаштувати його автоматично для цього мережевого інтерфейсу. Будь ласка, шукайте інструкції як це зробити вручну.",
|
|
||||||
"install_static_configure": "AdGuard Home виявив, що використовується динамічна IP-адреса — <0>{{ip}}</0>. Ви хочете встановити її як свою статичну адресу?",
|
|
||||||
"confirm_static_ip": "AdGuard Home налаштує {{ip}} як вашу статичну IP-адресу. Ви хочете продовжити?",
|
|
||||||
"list_updated": "{{count}} список оновлено",
|
|
||||||
"list_updated_plural": "{{count}} списки оновлено",
|
|
||||||
"dnssec_enable": "Увімкнути DNSSEC",
|
|
||||||
"dnssec_enable_desc": "Встановити прапорець DNSSEC для вихідних DNS запитів та перевірити результат (потрібен розпізнавач з підтримкою DNSSEC).",
|
|
||||||
"validated_with_dnssec": "Засвідчено DNSSEC",
|
|
||||||
"all_queries": "Усі запити",
|
|
||||||
"show_blocked_responses": "Заблоковані",
|
|
||||||
"show_whitelisted_responses": "Дозволені",
|
|
||||||
"show_processed_responses": "Оброблені",
|
|
||||||
"blocked_safebrowsing": "Безпечний перегляд заблоковано",
|
|
||||||
"blocked_adult_websites": "Заблоковані вебсайти для дорослих",
|
|
||||||
"blocked_threats": "Заблоковано загроз",
|
|
||||||
"allowed": "Дозволено",
|
|
||||||
"filtered": "Відфільтровано",
|
|
||||||
"rewritten": "Перезаписано",
|
|
||||||
"safe_search": "Безпечний пошук",
|
|
||||||
"blocklist": "Список блокування",
|
|
||||||
"milliseconds_abbreviation": "мс",
|
|
||||||
"cache_size": "Розмір кешу",
|
|
||||||
"cache_size_desc": "Розмір кешу DNS (у байтах)",
|
|
||||||
"cache_ttl_min_override": "Замінити мінімальний TTL",
|
|
||||||
"cache_ttl_max_override": "Замінити максимальний TTL",
|
|
||||||
"enter_cache_size": "Введіть розмір кешу (байт)",
|
|
||||||
"enter_cache_ttl_min_override": "Введіть мінімальний TTL (секунди)",
|
|
||||||
"enter_cache_ttl_max_override": "Введіть максимальний TTL (секунди)",
|
|
||||||
"cache_ttl_min_override_desc": "Розширити короткі значення time-to-live (секунди) отримані від основного сервера під час кешування відповідей DNS",
|
|
||||||
"cache_ttl_max_override_desc": "Встановіть максимальне значення time-to-live (секунди) для записів у кеші DNS",
|
|
||||||
"ttl_cache_validation": "Мінімальне значення TTL кеш-пам'яті має бути меншим або рівним максимальному значенню",
|
|
||||||
"cache_optimistic": "Оптимістичне кешування",
|
|
||||||
"cache_optimistic_desc": "AdGuard Home буде відповідати з кешу, навіть якщо відповіді в ньому застарілі, а також спробує оновити їх.",
|
|
||||||
"filter_category_general": "Загальні",
|
|
||||||
"filter_category_security": "Безпека",
|
|
||||||
"filter_category_regional": "Регіональні",
|
|
||||||
"filter_category_other": "Інші",
|
|
||||||
"filter_category_general_desc": "Списки, які блокують відстеження та рекламу на більшості пристроїв",
|
|
||||||
"filter_category_security_desc": "Фільтри, які спеціалізуються на блокуванні зловмисних програм, фішингу та шахрайських доменів",
|
|
||||||
"filter_category_regional_desc": "Списки, орієнтовані на регіональні оголошення та сервери відстеження",
|
|
||||||
"filter_category_other_desc": "Інші списки блокувань",
|
|
||||||
"setup_config_to_enable_dhcp_server": "Налаштуйте конфігурацію для увімкнення DHCP-сервера",
|
|
||||||
"original_response": "Оригінальна відповідь",
|
|
||||||
"click_to_view_queries": "Клацніть, щоб переглянути запити",
|
|
||||||
"port_53_faq_link": "Порт 53 часто зайнятий службами «DNSStubListener» або «systemd-resolved». <0>Як це вирішити</0>.",
|
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home буде видаляти всі запити DNS із цього клієнта.",
|
|
||||||
"filter_allowlist": "ПОПЕРЕДЖЕННЯ: Таким чином ви також виключите правило «{{disallowed_rule}}» зі списку дозволених клієнтів.",
|
|
||||||
"last_rule_in_allowlist": "Неможливо заблокувати цього клієнта, тому що правило «{{disallowed_rule}}» ВИМКНЕ режим списку дозволів.",
|
|
||||||
"experimental": "Експериментальний",
|
|
||||||
"use_saved_key": "Використати раніше збережений ключ"
|
|
||||||
}
|
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "Mạng con \"{{cidr}}\" không chứa địa chỉ IP \"{{ip}}\"",
|
"form_error_subnet": "Mạng con \"{{cidr}}\" không chứa địa chỉ IP \"{{ip}}\"",
|
||||||
"form_error_positive": "Phải lớn hơn 0",
|
"form_error_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",
|
||||||
"dhcp_form_gateway_input": "Cổng IP",
|
"dhcp_form_gateway_input": "Cổng IP",
|
||||||
"dhcp_form_subnet_input": "Mặt nạ mạng con",
|
"dhcp_form_subnet_input": "Mặt nạ mạng con",
|
||||||
"dhcp_form_range_title": "Phạm vi của địa chỉ IP",
|
"dhcp_form_range_title": "Phạm vi của địa chỉ IP",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
|
"form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
|
||||||
"form_error_positive": "必须大于 0",
|
"form_error_positive": "必须大于 0",
|
||||||
"form_error_negative": "必须大于等于 0",
|
"form_error_negative": "必须大于等于 0",
|
||||||
|
"range_end_error": "必须大于范围起始值",
|
||||||
"dhcp_form_gateway_input": "网关 IP",
|
"dhcp_form_gateway_input": "网关 IP",
|
||||||
"dhcp_form_subnet_input": "子网掩码",
|
"dhcp_form_subnet_input": "子网掩码",
|
||||||
"dhcp_form_range_title": "IP 地址范围",
|
"dhcp_form_range_title": "IP 地址范围",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含 IP 位址 \"{{ip}}\"",
|
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含 IP 位址 \"{{ip}}\"",
|
||||||
"form_error_positive": "數值必須大於 0",
|
"form_error_positive": "數值必須大於 0",
|
||||||
"form_error_negative": "數值必須大於等於 0",
|
"form_error_negative": "數值必須大於等於 0",
|
||||||
|
"range_end_error": "必須大於起始值",
|
||||||
"dhcp_form_gateway_input": "閘道 IP 位址",
|
"dhcp_form_gateway_input": "閘道 IP 位址",
|
||||||
"dhcp_form_subnet_input": "子網路遮罩",
|
"dhcp_form_subnet_input": "子網路遮罩",
|
||||||
"dhcp_form_range_title": "IP 位址範圍",
|
"dhcp_form_range_title": "IP 位址範圍",
|
||||||
@@ -611,5 +612,6 @@
|
|||||||
"click_to_view_queries": "按一下以檢視查詢結果",
|
"click_to_view_queries": "按一下以檢視查詢結果",
|
||||||
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
|
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
|
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
|
||||||
|
"client_not_in_allowed_clients": "此用戶端不被允許,它不在\"允許的用戶端\"列表中。",
|
||||||
"experimental": "實驗性"
|
"experimental": "實驗性"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,24 +36,16 @@
|
|||||||
"dhcp_ipv4_settings": "DHCP IPv4 設定",
|
"dhcp_ipv4_settings": "DHCP IPv4 設定",
|
||||||
"dhcp_ipv6_settings": "DHCP IPv6 設定",
|
"dhcp_ipv6_settings": "DHCP IPv6 設定",
|
||||||
"form_error_required": "必填的欄位",
|
"form_error_required": "必填的欄位",
|
||||||
"form_error_ip4_format": "無效的 IPv4 位址",
|
"form_error_ip4_format": "無效的 IPv4 格式",
|
||||||
"form_error_ip4_range_start_format": "無效起始範圍的 IPv4 位址",
|
"form_error_ip6_format": "無效的 IPv6 格式",
|
||||||
"form_error_ip4_range_end_format": "無效結束範圍的 IPv4 位址",
|
"form_error_ip_format": "無效的 IP 格式",
|
||||||
"form_error_ip4_gateway_format": "無效閘道的 IPv4 位址",
|
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
||||||
"form_error_ip6_format": "無效的 IPv6 位址",
|
"form_error_client_id_format": "無效的用戶端 ID 格式",
|
||||||
"form_error_ip_format": "無效的 IP 位址",
|
|
||||||
"form_error_mac_format": "無效的媒體存取控制(MAC)位址",
|
|
||||||
"form_error_client_id_format": "無效的用戶端 ID",
|
|
||||||
"form_error_server_name": "無效的伺服器名稱",
|
"form_error_server_name": "無效的伺服器名稱",
|
||||||
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
|
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
|
||||||
"form_error_positive": "必須大於 0",
|
"form_error_positive": "必須大於 0",
|
||||||
"form_error_negative": "必須等於或大於 0",
|
"form_error_negative": "必須等於或大於 0",
|
||||||
"out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外",
|
"range_end_error": "必須大於起始範圍",
|
||||||
"lower_range_start_error": "必須低於起始範圍",
|
|
||||||
"greater_range_start_error": "必須大於起始範圍",
|
|
||||||
"greater_range_end_error": "必須大於結束範圍",
|
|
||||||
"subnet_error": "位址必須在子網路中",
|
|
||||||
"gateway_or_subnet_invalid": "無效的子網路遮罩",
|
|
||||||
"dhcp_form_gateway_input": "閘道 IP",
|
"dhcp_form_gateway_input": "閘道 IP",
|
||||||
"dhcp_form_subnet_input": "子網路遮罩",
|
"dhcp_form_subnet_input": "子網路遮罩",
|
||||||
"dhcp_form_range_title": "IP 位址範圍",
|
"dhcp_form_range_title": "IP 位址範圍",
|
||||||
@@ -223,8 +215,8 @@
|
|||||||
"block": "封鎖",
|
"block": "封鎖",
|
||||||
"disallow_this_client": "不允許此用戶端",
|
"disallow_this_client": "不允許此用戶端",
|
||||||
"allow_this_client": "允許此用戶端",
|
"allow_this_client": "允許此用戶端",
|
||||||
"block_for_this_client_only": "僅對此用戶端封鎖",
|
"block_for_this_client_only": "僅封鎖此用戶端",
|
||||||
"unblock_for_this_client_only": "僅對此用戶端解除封鎖",
|
"unblock_for_this_client_only": "僅解除封鎖此用戶端",
|
||||||
"time_table_header": "時間",
|
"time_table_header": "時間",
|
||||||
"date": "日期",
|
"date": "日期",
|
||||||
"domain_name_table_header": "域名",
|
"domain_name_table_header": "域名",
|
||||||
@@ -537,7 +529,7 @@
|
|||||||
"blocked_by_cname_or_ip": "被正規名稱(CNAME)或 IP 封鎖",
|
"blocked_by_cname_or_ip": "被正規名稱(CNAME)或 IP 封鎖",
|
||||||
"try_again": "再次嘗試",
|
"try_again": "再次嘗試",
|
||||||
"domain_desc": "輸入您想要被改寫的域名或萬用字元(wildcard)。",
|
"domain_desc": "輸入您想要被改寫的域名或萬用字元(wildcard)。",
|
||||||
"example_rewrite_domain": "僅對此域名改寫回應。",
|
"example_rewrite_domain": "僅對於此域名改寫回應。",
|
||||||
"example_rewrite_wildcard": "對於所有的 <0>example.org</0> 子網域改寫回應。",
|
"example_rewrite_wildcard": "對於所有的 <0>example.org</0> 子網域改寫回應。",
|
||||||
"rewrite_ip_address": "IP 位址:在一個 A 或 AAAA 回應中使用此 IP",
|
"rewrite_ip_address": "IP 位址:在一個 A 或 AAAA 回應中使用此 IP",
|
||||||
"rewrite_domain_name": "域名:新增一筆正規名稱(CNAME)記錄",
|
"rewrite_domain_name": "域名:新增一筆正規名稱(CNAME)記錄",
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import {
|
|||||||
validateIpv4,
|
validateIpv4,
|
||||||
validateRequiredValue,
|
validateRequiredValue,
|
||||||
validateIpv4RangeEnd,
|
validateIpv4RangeEnd,
|
||||||
validateGatewaySubnetMask,
|
|
||||||
validateIpForGatewaySubnetMask,
|
|
||||||
validateNotInRange,
|
|
||||||
} from '../../../helpers/validators';
|
} from '../../../helpers/validators';
|
||||||
|
|
||||||
const FormDHCPv4 = ({
|
const FormDHCPv4 = ({
|
||||||
@@ -57,11 +54,7 @@ const FormDHCPv4 = ({
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t(ipv4placeholders.gateway_ip)}
|
placeholder={t(ipv4placeholders.gateway_ip)}
|
||||||
validate={[
|
validate={[validateIpv4, validateRequired]}
|
||||||
validateIpv4,
|
|
||||||
validateRequired,
|
|
||||||
validateNotInRange,
|
|
||||||
]}
|
|
||||||
disabled={!isInterfaceIncludesIpv4}
|
disabled={!isInterfaceIncludesIpv4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,11 +66,7 @@ const FormDHCPv4 = ({
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t(ipv4placeholders.subnet_mask)}
|
placeholder={t(ipv4placeholders.subnet_mask)}
|
||||||
validate={[
|
validate={[validateIpv4, validateRequired]}
|
||||||
validateIpv4,
|
|
||||||
validateRequired,
|
|
||||||
validateGatewaySubnetMask,
|
|
||||||
]}
|
|
||||||
disabled={!isInterfaceIncludesIpv4}
|
disabled={!isInterfaceIncludesIpv4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,11 +84,7 @@ const FormDHCPv4 = ({
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t(ipv4placeholders.range_start)}
|
placeholder={t(ipv4placeholders.range_start)}
|
||||||
validate={[
|
validate={[validateIpv4]}
|
||||||
validateIpv4,
|
|
||||||
validateGatewaySubnetMask,
|
|
||||||
validateIpForGatewaySubnetMask,
|
|
||||||
]}
|
|
||||||
disabled={!isInterfaceIncludesIpv4}
|
disabled={!isInterfaceIncludesIpv4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,12 +95,7 @@ const FormDHCPv4 = ({
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t(ipv4placeholders.range_end)}
|
placeholder={t(ipv4placeholders.range_end)}
|
||||||
validate={[
|
validate={[validateIpv4, validateIpv4RangeEnd]}
|
||||||
validateIpv4,
|
|
||||||
validateIpv4RangeEnd,
|
|
||||||
validateGatewaySubnetMask,
|
|
||||||
validateIpForGatewaySubnetMask,
|
|
||||||
]}
|
|
||||||
disabled={!isInterfaceIncludesIpv4}
|
disabled={!isInterfaceIncludesIpv4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,11 +53,7 @@ const Form = ({
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t('form_enter_subnet_ip', { cidr })}
|
placeholder={t('form_enter_subnet_ip', { cidr })}
|
||||||
validate={[
|
validate={[validateRequiredValue, validateIpv4, validateIpv4InCidr]}
|
||||||
validateRequiredValue,
|
|
||||||
validateIpv4,
|
|
||||||
validateIpv4InCidr,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form__group">
|
<div className="form__group">
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ const Modal = ({
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
processingAdding,
|
processingAdding,
|
||||||
cidr,
|
cidr,
|
||||||
rangeStart,
|
|
||||||
rangeEnd,
|
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@@ -40,14 +38,10 @@ const Modal = ({
|
|||||||
ip: '',
|
ip: '',
|
||||||
hostname: '',
|
hostname: '',
|
||||||
cidr,
|
cidr,
|
||||||
rangeStart,
|
|
||||||
rangeEnd,
|
|
||||||
}}
|
}}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
processingAdding={processingAdding}
|
processingAdding={processingAdding}
|
||||||
cidr={cidr}
|
cidr={cidr}
|
||||||
rangeStart={rangeStart}
|
|
||||||
rangeEnd={rangeEnd}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
@@ -59,8 +53,6 @@ Modal.propTypes = {
|
|||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
processingAdding: PropTypes.bool.isRequired,
|
processingAdding: PropTypes.bool.isRequired,
|
||||||
cidr: PropTypes.string.isRequired,
|
cidr: PropTypes.string.isRequired,
|
||||||
rangeStart: PropTypes.string,
|
|
||||||
rangeEnd: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Modal);
|
export default withTranslation()(Modal);
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ const StaticLeases = ({
|
|||||||
processingDeleting,
|
processingDeleting,
|
||||||
staticLeases,
|
staticLeases,
|
||||||
cidr,
|
cidr,
|
||||||
rangeStart,
|
|
||||||
rangeEnd,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -102,8 +100,6 @@ const StaticLeases = ({
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
processingAdding={processingAdding}
|
processingAdding={processingAdding}
|
||||||
cidr={cidr}
|
cidr={cidr}
|
||||||
rangeStart={rangeStart}
|
|
||||||
rangeEnd={rangeEnd}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -115,8 +111,6 @@ StaticLeases.propTypes = {
|
|||||||
processingAdding: PropTypes.bool.isRequired,
|
processingAdding: PropTypes.bool.isRequired,
|
||||||
processingDeleting: PropTypes.bool.isRequired,
|
processingDeleting: PropTypes.bool.isRequired,
|
||||||
cidr: PropTypes.string.isRequired,
|
cidr: PropTypes.string.isRequired,
|
||||||
rangeStart: PropTypes.string,
|
|
||||||
rangeEnd: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
cellWrap.propTypes = {
|
cellWrap.propTypes = {
|
||||||
|
|||||||
@@ -275,8 +275,6 @@ const Dhcp = () => {
|
|||||||
processingAdding={processingAdding}
|
processingAdding={processingAdding}
|
||||||
processingDeleting={processingDeleting}
|
processingDeleting={processingDeleting}
|
||||||
cidr={cidr}
|
cidr={cidr}
|
||||||
rangeStart={dhcp?.values?.v4?.range_start}
|
|
||||||
rangeEnd={dhcp?.values?.v4?.range_end}
|
|
||||||
/>
|
/>
|
||||||
<div className="btn-list mt-2">
|
<div className="btn-list mt-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
toNumber,
|
toNumber,
|
||||||
} from '../../../helpers/form';
|
} from '../../../helpers/form';
|
||||||
import {
|
import {
|
||||||
validateConfigClientId,
|
validateClientId,
|
||||||
validateServerName,
|
validateServerName,
|
||||||
validatePort,
|
validatePort,
|
||||||
validateIsSafePort,
|
validateIsSafePort,
|
||||||
@@ -132,7 +132,7 @@ const MobileConfigForm = ({ invalid }) => {
|
|||||||
component={renderInputField}
|
component={renderInputField}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={i18next.t('client_id_placeholder')}
|
placeholder={i18next.t('client_id_placeholder')}
|
||||||
validate={validateConfigClientId}
|
validate={validateClientId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const R_UNIX_ABSOLUTE_PATH = /^(\/[^/\x00]+)+$/;
|
|||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/;
|
export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/;
|
||||||
|
|
||||||
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
|
export const R_CLIENT_ID = /^[a-z0-9-]{1,64}$/;
|
||||||
|
|
||||||
export const HTML_PAGES = {
|
export const HTML_PAGES = {
|
||||||
INSTALL: '/install.html',
|
INSTALL: '/install.html',
|
||||||
|
|||||||
@@ -60,6 +60,12 @@
|
|||||||
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
|
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
|
||||||
"source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt"
|
"source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt"
|
||||||
},
|
},
|
||||||
|
"spam404": {
|
||||||
|
"name": "Spam404",
|
||||||
|
"categoryId": "security",
|
||||||
|
"homepage": "https://github.com/Spam404/lists",
|
||||||
|
"source": "https://raw.githubusercontent.com/Spam404/lists/master/main-blacklist.txt"
|
||||||
|
},
|
||||||
"nocoin-filter-list": {
|
"nocoin-filter-list": {
|
||||||
"name": "NoCoin Filter List",
|
"name": "NoCoin Filter List",
|
||||||
"categoryId": "security",
|
"categoryId": "security",
|
||||||
@@ -150,11 +156,11 @@
|
|||||||
"homepage": "https://github.com/ABPindo/indonesianadblockrules/",
|
"homepage": "https://github.com/ABPindo/indonesianadblockrules/",
|
||||||
"source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt"
|
"source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt"
|
||||||
},
|
},
|
||||||
"NLD-Easylist": {
|
"barb-block": {
|
||||||
"name": "NLD: Easylist",
|
"name": "BarbBlock",
|
||||||
"categoryId": "regional",
|
"categoryId": "other",
|
||||||
"homepage": "https://forums.lanik.us/viewforum.php?f=100",
|
"homepage": "https://github.com/paulgb/BarbBlock/",
|
||||||
"source": "https://easylist-downloads.adblockplus.org/easylistdutch.txt"
|
"source": "https://paulgb.github.io/BarbBlock/blacklists/hosts-file.txt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -552,20 +552,6 @@ export const isIpInCidr = (ip, cidr) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} subnetMask
|
|
||||||
* @returns {IPv4 | null}
|
|
||||||
*/
|
|
||||||
export const parseSubnetMask = (subnetMask) => {
|
|
||||||
try {
|
|
||||||
return ipaddr.parse(subnetMask).prefixLengthFromSubnetMask();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} subnetMask
|
* @param {string} subnetMask
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MAX_PORT,
|
MAX_PORT,
|
||||||
R_CIDR,
|
R_CIDR,
|
||||||
@@ -15,7 +14,7 @@ import {
|
|||||||
R_DOMAIN,
|
R_DOMAIN,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { ip4ToInt, isValidAbsolutePath } from './form';
|
import { ip4ToInt, isValidAbsolutePath } from './form';
|
||||||
import { isIpInCidr, parseSubnetMask } from './helpers';
|
import { isIpInCidr } from './helpers';
|
||||||
|
|
||||||
// Validation functions
|
// Validation functions
|
||||||
// https://redux-form.com/8.3.0/examples/fieldlevelvalidation/
|
// https://redux-form.com/8.3.0/examples/fieldlevelvalidation/
|
||||||
@@ -45,7 +44,7 @@ export const validateIpv4RangeEnd = (_, allValues) => {
|
|||||||
const { range_end, range_start } = allValues.v4;
|
const { range_end, range_start } = allValues.v4;
|
||||||
|
|
||||||
if (ip4ToInt(range_end) <= ip4ToInt(range_start)) {
|
if (ip4ToInt(range_end) <= ip4ToInt(range_start)) {
|
||||||
return 'greater_range_start_error';
|
return 'range_end_error';
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -62,85 +61,6 @@ export const validateIpv4 = (value) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {undefined|string}
|
|
||||||
* @param _
|
|
||||||
* @param allValues
|
|
||||||
*/
|
|
||||||
export const validateNotInRange = (value, allValues) => {
|
|
||||||
const { range_start, range_end } = allValues.v4;
|
|
||||||
|
|
||||||
if (range_start && validateIpv4(range_start)) {
|
|
||||||
return 'form_error_ip4_range_start_format';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range_end && validateIpv4(range_end)) {
|
|
||||||
return 'form_error_ip4_range_end_format';
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAboveMin = range_start && ip4ToInt(value) >= ip4ToInt(range_start);
|
|
||||||
const isBelowMax = range_end && ip4ToInt(value) <= ip4ToInt(range_end);
|
|
||||||
|
|
||||||
if (isAboveMin && isBelowMax) {
|
|
||||||
return i18next.t('out_of_range_error', {
|
|
||||||
start: range_start,
|
|
||||||
end: range_end,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!range_end && isAboveMin) {
|
|
||||||
return 'lower_range_start_error';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!range_start && isBelowMax) {
|
|
||||||
return 'greater_range_end_error';
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {undefined|string}
|
|
||||||
* @param _
|
|
||||||
* @param allValues
|
|
||||||
*/
|
|
||||||
export const validateGatewaySubnetMask = (_, allValues) => {
|
|
||||||
if (!allValues || !allValues.v4 || !allValues.v4.subnet_mask || !allValues.v4.gateway_ip) {
|
|
||||||
return 'gateway_or_subnet_invalid';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { subnet_mask, gateway_ip } = allValues.v4;
|
|
||||||
|
|
||||||
if (validateIpv4(gateway_ip)) {
|
|
||||||
return 'form_error_ip4_gateway_format';
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseSubnetMask(subnet_mask) ? undefined : 'gateway_or_subnet_invalid';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {undefined|string}
|
|
||||||
* @param value
|
|
||||||
* @param allValues
|
|
||||||
*/
|
|
||||||
export const validateIpForGatewaySubnetMask = (value, allValues) => {
|
|
||||||
if (!allValues || !allValues.v4 || !value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
gateway_ip, subnet_mask,
|
|
||||||
} = allValues.v4;
|
|
||||||
|
|
||||||
const subnetPrefix = parseSubnetMask(subnet_mask);
|
|
||||||
|
|
||||||
if (!isIpInCidr(value, `${gateway_ip}/${subnetPrefix}`)) {
|
|
||||||
return 'subnet_error';
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value {string}
|
* @param value {string}
|
||||||
* @returns {undefined|string}
|
* @returns {undefined|string}
|
||||||
@@ -163,21 +83,6 @@ export const validateClientId = (value) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param value {string}
|
|
||||||
* @returns {undefined|string}
|
|
||||||
*/
|
|
||||||
export const validateConfigClientId = (value) => {
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const formattedValue = value.trim();
|
|
||||||
if (formattedValue && !R_CLIENT_ID.test(formattedValue)) {
|
|
||||||
return 'form_error_client_id_format';
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value {string}
|
* @param value {string}
|
||||||
* @returns {undefined|string}
|
* @returns {undefined|string}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import de from './__locales/de.json';
|
|||||||
import en from './__locales/en.json';
|
import en from './__locales/en.json';
|
||||||
import es from './__locales/es.json';
|
import es from './__locales/es.json';
|
||||||
import fa from './__locales/fa.json';
|
import fa from './__locales/fa.json';
|
||||||
import fi from './__locales/fi.json';
|
|
||||||
import fr from './__locales/fr.json';
|
import fr from './__locales/fr.json';
|
||||||
import hr from './__locales/hr.json';
|
import hr from './__locales/hr.json';
|
||||||
import hu from './__locales/hu.json';
|
import hu from './__locales/hu.json';
|
||||||
@@ -34,7 +33,6 @@ import srCS from './__locales/sr-cs.json';
|
|||||||
import sv from './__locales/sv.json';
|
import sv from './__locales/sv.json';
|
||||||
import th from './__locales/th.json';
|
import th from './__locales/th.json';
|
||||||
import tr from './__locales/tr.json';
|
import tr from './__locales/tr.json';
|
||||||
import uk from './__locales/uk.json';
|
|
||||||
import vi from './__locales/vi.json';
|
import vi from './__locales/vi.json';
|
||||||
import zhCN from './__locales/zh-cn.json';
|
import zhCN from './__locales/zh-cn.json';
|
||||||
import zhHK from './__locales/zh-hk.json';
|
import zhHK from './__locales/zh-hk.json';
|
||||||
@@ -42,42 +40,108 @@ import zhTW from './__locales/zh-tw.json';
|
|||||||
import { setHtmlLangAttr } from './helpers/helpers';
|
import { setHtmlLangAttr } from './helpers/helpers';
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
be: { translation: be },
|
en: {
|
||||||
bg: { translation: bg },
|
translation: en,
|
||||||
cs: { translation: cs },
|
},
|
||||||
da: { translation: da },
|
enUS: {
|
||||||
de: { translation: de },
|
translation: en,
|
||||||
en: { translation: en },
|
},
|
||||||
'en-us': { translation: en },
|
vi: {
|
||||||
es: { translation: es },
|
translation: vi,
|
||||||
fa: { translation: fa },
|
},
|
||||||
fi: { translation: fi },
|
ru: {
|
||||||
fr: { translation: fr },
|
translation: ru,
|
||||||
hr: { translation: hr },
|
},
|
||||||
hu: { translation: hu },
|
es: {
|
||||||
id: { translation: id },
|
translation: es,
|
||||||
it: { translation: it },
|
},
|
||||||
ja: { translation: ja },
|
fr: {
|
||||||
ko: { translation: ko },
|
translation: fr,
|
||||||
nl: { translation: nl },
|
},
|
||||||
no: { translation: no },
|
ja: {
|
||||||
pl: { translation: pl },
|
translation: ja,
|
||||||
'pt-br': { translation: ptBR },
|
},
|
||||||
'pt-pt': { translation: ptPT },
|
sv: {
|
||||||
ro: { translation: ro },
|
translation: sv,
|
||||||
ru: { translation: ru },
|
},
|
||||||
'si-lk': { translation: siLk },
|
'pt-br': {
|
||||||
sk: { translation: sk },
|
translation: ptBR,
|
||||||
sl: { translation: sl },
|
},
|
||||||
'sr-cs': { translation: srCS },
|
'zh-hk': {
|
||||||
sv: { translation: sv },
|
translation: zhHK,
|
||||||
th: { translation: th },
|
},
|
||||||
tr: { translation: tr },
|
'zh-tw': {
|
||||||
uk: { translation: uk },
|
translation: zhTW,
|
||||||
vi: { translation: vi },
|
},
|
||||||
'zh-cn': { translation: zhCN },
|
bg: {
|
||||||
'zh-hk': { translation: zhHK },
|
translation: bg,
|
||||||
'zh-tw': { translation: zhTW },
|
},
|
||||||
|
be: {
|
||||||
|
translation: be,
|
||||||
|
},
|
||||||
|
'zh-cn': {
|
||||||
|
translation: zhCN,
|
||||||
|
},
|
||||||
|
cs: {
|
||||||
|
translation: cs,
|
||||||
|
},
|
||||||
|
da: {
|
||||||
|
translation: da,
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
translation: de,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
translation: id,
|
||||||
|
},
|
||||||
|
it: {
|
||||||
|
translation: it,
|
||||||
|
},
|
||||||
|
ko: {
|
||||||
|
translation: ko,
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
translation: no,
|
||||||
|
},
|
||||||
|
nl: {
|
||||||
|
translation: nl,
|
||||||
|
},
|
||||||
|
pl: {
|
||||||
|
translation: pl,
|
||||||
|
},
|
||||||
|
'pt-pt': {
|
||||||
|
translation: ptPT,
|
||||||
|
},
|
||||||
|
sk: {
|
||||||
|
translation: sk,
|
||||||
|
},
|
||||||
|
sl: {
|
||||||
|
translation: sl,
|
||||||
|
},
|
||||||
|
tr: {
|
||||||
|
translation: tr,
|
||||||
|
},
|
||||||
|
'sr-cs': {
|
||||||
|
translation: srCS,
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
translation: hr,
|
||||||
|
},
|
||||||
|
hu: {
|
||||||
|
translation: hu,
|
||||||
|
},
|
||||||
|
fa: {
|
||||||
|
translation: fa,
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
translation: th,
|
||||||
|
},
|
||||||
|
ro: {
|
||||||
|
translation: ro,
|
||||||
|
},
|
||||||
|
'si-lk': {
|
||||||
|
translation: siLk,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const availableLanguages = Object.keys(LANGUAGES);
|
const availableLanguages = Object.keys(LANGUAGES);
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -3,23 +3,21 @@ module github.com/AdguardTeam/AdGuardHome
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.9
|
github.com/AdguardTeam/dnsproxy v0.39.8
|
||||||
github.com/AdguardTeam/golibs v0.10.2
|
github.com/AdguardTeam/golibs v0.9.3
|
||||||
github.com/AdguardTeam/urlfilter v0.15.0
|
github.com/AdguardTeam/urlfilter v0.14.6
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.2
|
github.com/ameshkov/dnscrypt/v2 v2.2.2
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020
|
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020
|
||||||
github.com/google/go-cmp v0.5.5
|
github.com/google/go-cmp v0.5.5
|
||||||
github.com/google/gopacket v1.1.19
|
|
||||||
github.com/google/renameio v1.0.1
|
github.com/google/renameio v1.0.1
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20210310193751-cfd4d47082c2
|
github.com/insomniacslk/dhcp v0.0.0-20210310193751-cfd4d47082c2
|
||||||
github.com/kardianos/service v1.2.0
|
github.com/kardianos/service v1.2.0
|
||||||
github.com/lucas-clemente/quic-go v0.21.1
|
github.com/lucas-clemente/quic-go v0.21.1
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
|
||||||
github.com/mdlayher/netlink v1.4.0
|
github.com/mdlayher/netlink v1.4.0
|
||||||
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf
|
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/stretchr/objx v0.1.1 // indirect
|
github.com/stretchr/objx v0.1.1 // indirect
|
||||||
@@ -27,8 +25,8 @@ require (
|
|||||||
github.com/ti-mo/netfilter v0.4.0
|
github.com/ti-mo/netfilter v0.4.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a
|
||||||
golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06
|
howett.net/plist v0.0.0-20201203080718-1454fab16a06
|
||||||
|
|||||||
48
go.sum
48
go.sum
@@ -9,22 +9,22 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
|||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf h1:gc042VRSIRSUzZ+Px6xQCRWNJZTaPkomisDfUZmoFNk=
|
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf h1:gc042VRSIRSUzZ+Px6xQCRWNJZTaPkomisDfUZmoFNk=
|
||||||
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
github.com/AdguardTeam/dhcp v0.0.0-20210519141215-51808c73c0bf/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.9 h1:lH4lKA7KHKFJZgzlij1YAVX6v7eIQpUFpYh9qV+WfGw=
|
github.com/AdguardTeam/dnsproxy v0.39.8 h1:miRhkZBx/19Rs1o10r3QC0D0Zc2J2Id/cqXwfvLOyM0=
|
||||||
github.com/AdguardTeam/dnsproxy v0.39.9/go.mod h1:eDpJKAdkHORRwAedjuERv+7SWlcz4cn+5uwrbUAWHRY=
|
github.com/AdguardTeam/dnsproxy v0.39.8/go.mod h1:eDpJKAdkHORRwAedjuERv+7SWlcz4cn+5uwrbUAWHRY=
|
||||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.9.2/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
github.com/AdguardTeam/golibs v0.9.2/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
||||||
github.com/AdguardTeam/golibs v0.10.2 h1:TAwnS4Y49sSUa4UX1yz/MWNGbIlXHqafrWr9MxdIh9A=
|
github.com/AdguardTeam/golibs v0.9.3 h1:noeKHJEzrSwxzX0Zi3USM3cXf1qQV99SO772jet/uEY=
|
||||||
github.com/AdguardTeam/golibs v0.10.2/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
github.com/AdguardTeam/golibs v0.9.3/go.mod h1:fCAMwPBJ8S7YMYbTWvYS+eeTLblP5E04IDtNAo7y7IY=
|
||||||
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.15.0 h1:K3WWZE0K5nPTHe2l+TRXDFpYWJJnvkHdlWidt6NQUTk=
|
github.com/AdguardTeam/urlfilter v0.14.6 h1:emqoKZElooHACYehRBYENeKVN1a/rspxiqTIMYLuoIo=
|
||||||
github.com/AdguardTeam/urlfilter v0.15.0/go.mod h1:EwXwrYhowP7bedqmOrmKKmQtpBYFyDNEBFQ+lxdUgQU=
|
github.com/AdguardTeam/urlfilter v0.14.6/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/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
@@ -60,8 +60,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
|||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020 h1:mdi6AbCEoKCA1xKCmp7UtRB5fvGFlP92PvlhxgdvXEw=
|
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020 h1:mdi6AbCEoKCA1xKCmp7UtRB5fvGFlP92PvlhxgdvXEw=
|
||||||
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020/go.mod h1:KmHOjTUmJh/l04ukqPoBWPEZr9jwN05h5NXQl5C+DyY=
|
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020/go.mod h1:KmHOjTUmJh/l04ukqPoBWPEZr9jwN05h5NXQl5C+DyY=
|
||||||
github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
|
github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
|
||||||
@@ -93,8 +93,6 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
|
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
|
||||||
@@ -109,6 +107,7 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8
|
|||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||||
github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw=
|
github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw=
|
||||||
github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=
|
github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||||
@@ -169,6 +168,7 @@ github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZ
|
|||||||
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf h1:InctQoB89TIkmgIFQeIL4KXNvWc1iebQXdZggqPSwL8=
|
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf h1:InctQoB89TIkmgIFQeIL4KXNvWc1iebQXdZggqPSwL8=
|
||||||
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
@@ -203,8 +203,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
|||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
|
||||||
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
@@ -244,10 +244,6 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
|
|||||||
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
|
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
|
||||||
github.com/ti-mo/netfilter v0.4.0 h1:rTN1nBYULDmMfDeBHZpKuNKX/bWEXQUhe02a/10orzg=
|
github.com/ti-mo/netfilter v0.4.0 h1:rTN1nBYULDmMfDeBHZpKuNKX/bWEXQUhe02a/10orzg=
|
||||||
github.com/ti-mo/netfilter v0.4.0/go.mod h1:V54q75mUx8CNA2JnFl+wv9iZ5+JP9nCcRlaFS5OZSRM=
|
github.com/ti-mo/netfilter v0.4.0/go.mod h1:V54q75mUx8CNA2JnFl+wv9iZ5+JP9nCcRlaFS5OZSRM=
|
||||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
|
||||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
|
||||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
|
||||||
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
||||||
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
@@ -272,7 +268,6 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
|
|||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -305,9 +300,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210908191846-a5e095526f91/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
|
||||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 h1:Z04ewVs7JhXaYkmDhBERPi41gnltfQpMWDnTnQbaCqk=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -333,7 +327,6 @@ golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -363,18 +356,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
|
||||||
golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3 h1:3Ad41xy2WCESpufXwgs7NpDSu+vjxqLt2UFqUV+20bI=
|
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -386,7 +377,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type LimitReachedError struct {
|
|||||||
Limit int64
|
Limit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the error interface for LimitReachedError.
|
// Error implements error interface for LimitReachedError.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Think about error string format.
|
// TODO(a.garipov): Think about error string format.
|
||||||
func (lre *LimitReachedError) Error() string {
|
func (lre *LimitReachedError) Error() string {
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
package aghio
|
package aghio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLimitReader(t *testing.T) {
|
func TestLimitReader(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
wantErrMsg string
|
want error
|
||||||
name string
|
name string
|
||||||
n int64
|
n int64
|
||||||
}{{
|
}{{
|
||||||
wantErrMsg: "",
|
want: nil,
|
||||||
name: "positive",
|
name: "positive",
|
||||||
n: 1,
|
n: 1,
|
||||||
}, {
|
}, {
|
||||||
wantErrMsg: "",
|
want: nil,
|
||||||
name: "zero",
|
name: "zero",
|
||||||
n: 0,
|
n: 0,
|
||||||
}, {
|
}, {
|
||||||
wantErrMsg: "aghio: invalid n in LimitReader: -1",
|
want: fmt.Errorf("aghio: invalid n in LimitReader: -1"),
|
||||||
name: "negative",
|
name: "negative",
|
||||||
n: -1,
|
n: -1,
|
||||||
}}
|
}}
|
||||||
@@ -32,7 +32,7 @@ func TestLimitReader(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) {
|
||||||
_, err := LimitReader(nil, tc.n)
|
_, err := LimitReader(nil, tc.n)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
assert.Equal(t, tc.want, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,23 +73,36 @@ func TestLimitedReader_Read(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
readCloser := io.NopCloser(strings.NewReader(tc.rStr))
|
readCloser := io.NopCloser(strings.NewReader(tc.rStr))
|
||||||
|
buf := make([]byte, tc.limit+1)
|
||||||
|
|
||||||
lreader, err := LimitReader(readCloser, tc.limit)
|
lreader, err := LimitReader(readCloser, tc.limit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, lreader)
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
buf := make([]byte, tc.limit+1)
|
|
||||||
n, rerr := lreader.Read(buf)
|
|
||||||
require.Equal(t, rerr, tc.err)
|
|
||||||
|
|
||||||
|
n, err := lreader.Read(buf)
|
||||||
|
require.Equal(t, tc.err, err)
|
||||||
assert.Equal(t, tc.want, n)
|
assert.Equal(t, tc.want, n)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLimitedReader_LimitReachedError(t *testing.T) {
|
func TestLimitedReader_LimitReachedError(t *testing.T) {
|
||||||
testutil.AssertErrorMsg(t, "attempted to read more than 0 bytes", &LimitReachedError{
|
testCases := []struct {
|
||||||
|
err error
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
err: &LimitReachedError{
|
||||||
Limit: 0,
|
Limit: 0,
|
||||||
|
},
|
||||||
|
name: "simplest",
|
||||||
|
want: "attempted to read more than 0 bytes",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, tc.err.Error())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
387
internal/aghnet/etchostscontainer.go
Normal file
387
internal/aghnet/etchostscontainer.go
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
package aghnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type onChangedT func()
|
||||||
|
|
||||||
|
// EtcHostsContainer - automatic DNS records
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Move the logic under interface. Refactor. Probably remove
|
||||||
|
// the resolving logic.
|
||||||
|
type EtcHostsContainer struct {
|
||||||
|
// lock protects table and tableReverse.
|
||||||
|
lock sync.RWMutex
|
||||||
|
// table is the host-to-IPs map.
|
||||||
|
table map[string][]net.IP
|
||||||
|
// tableReverse is the IP-to-hosts map. The type of the values in the
|
||||||
|
// map is []string.
|
||||||
|
tableReverse *netutil.IPMap
|
||||||
|
|
||||||
|
hostsFn string // path to the main hosts-file
|
||||||
|
hostsDirs []string // paths to OS-specific directories with hosts-files
|
||||||
|
watcher *fsnotify.Watcher // file and directory watcher object
|
||||||
|
|
||||||
|
// onlyWritesChan used to contain only writing events from watcher.
|
||||||
|
onlyWritesChan chan fsnotify.Event
|
||||||
|
|
||||||
|
onChanged onChangedT // notification to other modules
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnChanged - set callback function that will be called when the data is changed
|
||||||
|
func (ehc *EtcHostsContainer) SetOnChanged(onChanged onChangedT) {
|
||||||
|
ehc.onChanged = onChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify other modules
|
||||||
|
func (ehc *EtcHostsContainer) notify() {
|
||||||
|
if ehc.onChanged == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ehc.onChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init - initialize
|
||||||
|
// hostsFn: Override default name for the hosts-file (optional)
|
||||||
|
func (ehc *EtcHostsContainer) Init(hostsFn string) {
|
||||||
|
ehc.table = make(map[string][]net.IP)
|
||||||
|
ehc.onlyWritesChan = make(chan fsnotify.Event, 2)
|
||||||
|
|
||||||
|
ehc.hostsFn = "/etc/hosts"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
ehc.hostsFn = os.ExpandEnv("$SystemRoot\\system32\\drivers\\etc\\hosts")
|
||||||
|
}
|
||||||
|
if len(hostsFn) != 0 {
|
||||||
|
ehc.hostsFn = hostsFn
|
||||||
|
}
|
||||||
|
|
||||||
|
if aghos.IsOpenWrt() {
|
||||||
|
// OpenWrt: "/tmp/hosts/dhcp.cfg01411c".
|
||||||
|
ehc.hostsDirs = append(ehc.hostsDirs, "/tmp/hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load hosts initially
|
||||||
|
ehc.updateHosts()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ehc.watcher, err = fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("etchosts: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start - start module
|
||||||
|
func (ehc *EtcHostsContainer) Start() {
|
||||||
|
if ehc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Start etchostscontainer module")
|
||||||
|
|
||||||
|
ehc.updateHosts()
|
||||||
|
|
||||||
|
if ehc.watcher != nil {
|
||||||
|
go ehc.watcherLoop()
|
||||||
|
|
||||||
|
err := ehc.watcher.Add(ehc.hostsFn)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error while initializing watcher for a file %s: %s", ehc.hostsFn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dir := range ehc.hostsDirs {
|
||||||
|
err = ehc.watcher.Add(dir)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error while initializing watcher for a directory %s: %s", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close - close module
|
||||||
|
func (ehc *EtcHostsContainer) Close() {
|
||||||
|
if ehc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ehc.watcher != nil {
|
||||||
|
_ = ehc.watcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't close onlyWritesChan here and let onlyWrites close it after
|
||||||
|
// watcher.Events is closed to prevent close races.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process returns the list of IP addresses for the hostname or nil if nothing
|
||||||
|
// found.
|
||||||
|
func (ehc *EtcHostsContainer) Process(host string, qtype uint16) []net.IP {
|
||||||
|
if qtype == dns.TypePTR {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipsCopy []net.IP
|
||||||
|
ehc.lock.RLock()
|
||||||
|
defer ehc.lock.RUnlock()
|
||||||
|
|
||||||
|
if ips, ok := ehc.table[host]; ok {
|
||||||
|
ipsCopy = make([]net.IP, len(ips))
|
||||||
|
copy(ipsCopy, ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("etchosts: answer: %s -> %v", host, ipsCopy)
|
||||||
|
return ipsCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessReverse processes a PTR request. It returns nil if nothing is found.
|
||||||
|
func (ehc *EtcHostsContainer) ProcessReverse(addr string, qtype uint16) (hosts []string) {
|
||||||
|
if qtype != dns.TypePTR {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := netutil.IPFromReversedAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("etchosts: reversed addr: %s", err)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ehc.lock.RLock()
|
||||||
|
defer ehc.lock.RUnlock()
|
||||||
|
|
||||||
|
v, ok := ehc.tableReverse.Get(ip)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, ok = v.([]string)
|
||||||
|
if !ok {
|
||||||
|
log.Error("etchosts: bad type %T in tableReverse for %s", v, ip)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else if len(hosts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("etchosts: reverse-lookup: %s -> %s", addr, hosts)
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns an IP-to-hostnames table. The type of the values in the map is
|
||||||
|
// []string. It is safe for concurrent use.
|
||||||
|
func (ehc *EtcHostsContainer) List() (ipToHosts *netutil.IPMap) {
|
||||||
|
ehc.lock.RLock()
|
||||||
|
defer ehc.lock.RUnlock()
|
||||||
|
|
||||||
|
return ehc.tableReverse.ShallowClone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// update table
|
||||||
|
func (ehc *EtcHostsContainer) updateTable(table map[string][]net.IP, host string, ipAddr net.IP) {
|
||||||
|
ips, ok := table[host]
|
||||||
|
if ok {
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.Equal(ipAddr) {
|
||||||
|
// IP already exists: don't add duplicates
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ips = append(ips, ipAddr)
|
||||||
|
table[host] = ips
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
table[host] = []net.IP{ipAddr}
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
log.Debug("etchosts: added %s -> %s", ipAddr, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTableRev updates the reverse address table.
|
||||||
|
func (ehc *EtcHostsContainer) updateTableRev(tableRev *netutil.IPMap, newHost string, ip net.IP) {
|
||||||
|
v, ok := tableRev.Get(ip)
|
||||||
|
if !ok {
|
||||||
|
tableRev.Set(ip, []string{newHost})
|
||||||
|
log.Debug("etchosts: added reverse-address %s -> %s", ip, newHost)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, _ := v.([]string)
|
||||||
|
for _, host := range hosts {
|
||||||
|
if host == newHost {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts = append(hosts, newHost)
|
||||||
|
tableRev.Set(ip, hosts)
|
||||||
|
|
||||||
|
log.Debug("etchosts: added reverse-address %s -> %s", ip, newHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHostsLine parses hosts from the fields.
|
||||||
|
func parseHostsLine(fields []string) (hosts []string) {
|
||||||
|
for _, f := range fields {
|
||||||
|
hashIdx := strings.IndexByte(f, '#')
|
||||||
|
if hashIdx == 0 {
|
||||||
|
// The rest of the fields are a part of the comment.
|
||||||
|
// Skip immediately.
|
||||||
|
return
|
||||||
|
} else if hashIdx > 0 {
|
||||||
|
// Only a part of the field is a comment.
|
||||||
|
hosts = append(hosts, f[:hashIdx])
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts = append(hosts, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// load reads IP-hostname pairs from the hosts file. Multiple hostnames per
|
||||||
|
// line for one IP are supported.
|
||||||
|
func (ehc *EtcHostsContainer) load(
|
||||||
|
table map[string][]net.IP,
|
||||||
|
tableRev *netutil.IPMap,
|
||||||
|
fn string,
|
||||||
|
) {
|
||||||
|
f, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("etchosts: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
derr := f.Close()
|
||||||
|
if derr != nil {
|
||||||
|
log.Error("etchosts: closing file: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Debug("etchosts: loading hosts from file %s", fn)
|
||||||
|
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
for s.Scan() {
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(fields[0])
|
||||||
|
if ip == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts := parseHostsLine(fields[1:])
|
||||||
|
for _, host := range hosts {
|
||||||
|
ehc.updateTable(table, host, ip)
|
||||||
|
ehc.updateTableRev(tableRev, host, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("etchosts: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onlyWrites is a filter for (*fsnotify.Watcher).Events.
|
||||||
|
func (ehc *EtcHostsContainer) onlyWrites() {
|
||||||
|
for event := range ehc.watcher.Events {
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
ehc.onlyWritesChan <- event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ehc.onlyWritesChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive notifications from fsnotify package
|
||||||
|
func (ehc *EtcHostsContainer) watcherLoop() {
|
||||||
|
go ehc.onlyWrites()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-ehc.onlyWritesChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that we sometimes have the same event occurred
|
||||||
|
// several times.
|
||||||
|
repeat := true
|
||||||
|
for repeat {
|
||||||
|
select {
|
||||||
|
case _, ok = <-ehc.onlyWritesChan:
|
||||||
|
repeat = ok
|
||||||
|
default:
|
||||||
|
repeat = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Debug("etchosts: modified: %s", event.Name)
|
||||||
|
ehc.updateHosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
case err, ok := <-ehc.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error("etchosts: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateHosts - loads system hosts
|
||||||
|
func (ehc *EtcHostsContainer) updateHosts() {
|
||||||
|
table := make(map[string][]net.IP)
|
||||||
|
tableRev := netutil.NewIPMap(0)
|
||||||
|
|
||||||
|
ehc.load(table, tableRev, ehc.hostsFn)
|
||||||
|
|
||||||
|
for _, dir := range ehc.hostsDirs {
|
||||||
|
des, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Error("etchosts: Opening directory: %q: %s", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, de := range des {
|
||||||
|
ehc.load(table, tableRev, filepath.Join(dir, de.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
ehc.lock.Lock()
|
||||||
|
defer ehc.lock.Unlock()
|
||||||
|
|
||||||
|
ehc.table = table
|
||||||
|
ehc.tableReverse = tableRev
|
||||||
|
}()
|
||||||
|
|
||||||
|
ehc.notify()
|
||||||
|
}
|
||||||
130
internal/aghnet/etchostscontainer_test.go
Normal file
130
internal/aghnet/etchostscontainer_test.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package aghnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
aghtest.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareTestFile(t *testing.T) (f *os.File) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
f, err := os.CreateTemp(dir, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, f)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.NoError(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.NoError(t, err)
|
||||||
|
assert.Equal(t, n, len(str))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcHostsContainerResolution(t *testing.T) {
|
||||||
|
ehc := &EtcHostsContainer{}
|
||||||
|
|
||||||
|
f := prepareTestFile(t)
|
||||||
|
|
||||||
|
assertWriting(t, f,
|
||||||
|
" 127.0.0.1 host localhost # comment \n",
|
||||||
|
" ::1 localhost#comment \n",
|
||||||
|
)
|
||||||
|
ehc.Init(f.Name())
|
||||||
|
|
||||||
|
t.Run("existing_host", func(t *testing.T) {
|
||||||
|
ips := ehc.Process("localhost", dns.TypeA)
|
||||||
|
require.Len(t, ips, 1)
|
||||||
|
assert.Equal(t, net.IPv4(127, 0, 0, 1), ips[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown_host", func(t *testing.T) {
|
||||||
|
ips := ehc.Process("newhost", dns.TypeA)
|
||||||
|
assert.Nil(t, ips)
|
||||||
|
|
||||||
|
// Comment.
|
||||||
|
ips = ehc.Process("comment", dns.TypeA)
|
||||||
|
assert.Nil(t, ips)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("hosts_file", func(t *testing.T) {
|
||||||
|
names, ok := ehc.List().Get(net.IP{127, 0, 0, 1})
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, []string{"host", "localhost"}, names)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ptr", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
wantIP string
|
||||||
|
wantHost string
|
||||||
|
wantLen int
|
||||||
|
}{
|
||||||
|
{wantIP: "127.0.0.1", wantHost: "host", wantLen: 2},
|
||||||
|
{wantIP: "::1", wantHost: "localhost", wantLen: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
a, err := dns.ReverseAddr(tc.wantIP)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
a = strings.TrimSuffix(a, ".")
|
||||||
|
hosts := ehc.ProcessReverse(a, dns.TypePTR)
|
||||||
|
require.Len(t, hosts, tc.wantLen)
|
||||||
|
assert.Equal(t, tc.wantHost, hosts[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcHostsContainerFSNotify(t *testing.T) {
|
||||||
|
ehc := &EtcHostsContainer{}
|
||||||
|
|
||||||
|
f := prepareTestFile(t)
|
||||||
|
|
||||||
|
assertWriting(t, f, " 127.0.0.1 host localhost \n")
|
||||||
|
ehc.Init(f.Name())
|
||||||
|
|
||||||
|
t.Run("unknown_host", func(t *testing.T) {
|
||||||
|
ips := ehc.Process("newhost", dns.TypeA)
|
||||||
|
assert.Nil(t, ips)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start monitoring for changes.
|
||||||
|
ehc.Start()
|
||||||
|
t.Cleanup(ehc.Close)
|
||||||
|
|
||||||
|
assertWriting(t, f, "127.0.0.2 newhost\n")
|
||||||
|
require.NoError(t, f.Sync())
|
||||||
|
|
||||||
|
// Wait until fsnotify has triggered and processed the file-modification
|
||||||
|
// event.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
t.Run("notified", func(t *testing.T) {
|
||||||
|
ips := ehc.Process("newhost", dns.TypeA)
|
||||||
|
assert.NotNil(t, ips)
|
||||||
|
require.Len(t, ips, 1)
|
||||||
|
assert.True(t, net.IP{127, 0, 0, 2}.Equal(ips[0]))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,438 +0,0 @@
|
|||||||
package aghnet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"net"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
|
||||||
"github.com/AdguardTeam/urlfilter"
|
|
||||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultHostsPaths returns the slice of paths default for the operating system
|
|
||||||
// to files and directories which are containing the hosts database. The result
|
|
||||||
// is intended to be used within fs.FS so the initial slash is omitted.
|
|
||||||
func DefaultHostsPaths() (paths []string) {
|
|
||||||
return defaultHostsPaths()
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostsContainerPref is a prefix for logging and wrapping errors in
|
|
||||||
// HostsContainer's methods.
|
|
||||||
const hostsContainerPref = "hosts container"
|
|
||||||
|
|
||||||
// HostsContainer stores the relevant hosts database provided by the OS and
|
|
||||||
// processes both A/AAAA and PTR DNS requests for those.
|
|
||||||
type HostsContainer struct {
|
|
||||||
// engLock protects rulesStrg and engine.
|
|
||||||
engLock *sync.RWMutex
|
|
||||||
|
|
||||||
// rulesStrg stores the rules obtained from the hosts' file.
|
|
||||||
rulesStrg *filterlist.RuleStorage
|
|
||||||
// engine serves rulesStrg.
|
|
||||||
engine *urlfilter.DNSEngine
|
|
||||||
|
|
||||||
// done is the channel to sign closing the container.
|
|
||||||
done chan struct{}
|
|
||||||
|
|
||||||
// updates is the channel for receiving updated hosts.
|
|
||||||
updates chan *netutil.IPMap
|
|
||||||
// last is the set of hosts that was cached within last detected change.
|
|
||||||
last *netutil.IPMap
|
|
||||||
|
|
||||||
// fsys is the working file system to read hosts files from.
|
|
||||||
fsys fs.FS
|
|
||||||
|
|
||||||
// w tracks the changes in specified files and directories.
|
|
||||||
w aghos.FSWatcher
|
|
||||||
// patterns stores specified paths in the fs.Glob-compatible form.
|
|
||||||
patterns []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNoHostsPaths is returned when there are no valid paths to watch passed to
|
|
||||||
// the HostsContainer.
|
|
||||||
const ErrNoHostsPaths errors.Error = "no valid paths to hosts files provided"
|
|
||||||
|
|
||||||
// NewHostsContainer creates a container of hosts, that watches the paths with
|
|
||||||
// w. paths shouldn't be empty and each of paths should locate either a file or
|
|
||||||
// a directory in fsys. fsys and w must be non-nil.
|
|
||||||
func NewHostsContainer(
|
|
||||||
fsys fs.FS,
|
|
||||||
w aghos.FSWatcher,
|
|
||||||
paths ...string,
|
|
||||||
) (hc *HostsContainer, err error) {
|
|
||||||
defer func() { err = errors.Annotate(err, "%s: %w", hostsContainerPref) }()
|
|
||||||
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return nil, ErrNoHostsPaths
|
|
||||||
}
|
|
||||||
|
|
||||||
var patterns []string
|
|
||||||
patterns, err = pathsToPatterns(fsys, paths)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(patterns) == 0 {
|
|
||||||
return nil, ErrNoHostsPaths
|
|
||||||
}
|
|
||||||
|
|
||||||
hc = &HostsContainer{
|
|
||||||
engLock: &sync.RWMutex{},
|
|
||||||
done: make(chan struct{}, 1),
|
|
||||||
updates: make(chan *netutil.IPMap, 1),
|
|
||||||
fsys: fsys,
|
|
||||||
w: w,
|
|
||||||
patterns: patterns,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("%s: starting", hostsContainerPref)
|
|
||||||
|
|
||||||
// Load initially.
|
|
||||||
if err = hc.refresh(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range paths {
|
|
||||||
if err = w.Add(p); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return nil, fmt.Errorf("adding path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("%s: file %q expected to exist but doesn't", hostsContainerPref, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go hc.handleEvents()
|
|
||||||
|
|
||||||
return hc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchRequest is the request processing method to resolve hostnames and
|
|
||||||
// addresses from the operating system's hosts files. res is nil for any
|
|
||||||
// request having not an A/AAAA or PTR type. It's safe for concurrent use.
|
|
||||||
func (hc *HostsContainer) MatchRequest(
|
|
||||||
req urlfilter.DNSRequest,
|
|
||||||
) (res *urlfilter.DNSResult, ok bool) {
|
|
||||||
switch req.DNSType {
|
|
||||||
case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
|
|
||||||
log.Debug("%s: handling the request", hostsContainerPref)
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
hc.engLock.RLock()
|
|
||||||
defer hc.engLock.RUnlock()
|
|
||||||
|
|
||||||
return hc.engine.MatchRequest(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the io.Closer interface for *HostsContainer. Close must
|
|
||||||
// only be called once. The returned err is always nil.
|
|
||||||
func (hc *HostsContainer) Close() (err error) {
|
|
||||||
log.Debug("%s: closing", hostsContainerPref)
|
|
||||||
|
|
||||||
close(hc.done)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upd returns the channel into which the updates are sent. The receivable
|
|
||||||
// map's values are guaranteed to be of type of *stringutil.Set.
|
|
||||||
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
|
||||||
return hc.updates
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathsToPatterns converts paths into patterns compatible with fs.Glob.
|
|
||||||
func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error) {
|
|
||||||
for i, p := range paths {
|
|
||||||
var fi fs.FileInfo
|
|
||||||
fi, err = fs.Stat(fsys, p)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't put a filename here since it's already added by fs.Stat.
|
|
||||||
return nil, fmt.Errorf("path at index %d: %w", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.IsDir() {
|
|
||||||
p = path.Join(p, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
patterns = append(patterns, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return patterns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEvents concurrently handles the events. It closes the update channel
|
|
||||||
// of HostsContainer when finishes. Used to be called within a goroutine.
|
|
||||||
func (hc *HostsContainer) handleEvents() {
|
|
||||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPref))
|
|
||||||
|
|
||||||
defer close(hc.updates)
|
|
||||||
|
|
||||||
ok, eventsCh := true, hc.w.Events()
|
|
||||||
for ok {
|
|
||||||
select {
|
|
||||||
case _, ok = <-eventsCh:
|
|
||||||
if !ok {
|
|
||||||
log.Debug("%s: watcher closed the events channel", hostsContainerPref)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := hc.refresh(); err != nil {
|
|
||||||
log.Error("%s: %s", hostsContainerPref, err)
|
|
||||||
}
|
|
||||||
case _, ok = <-hc.done:
|
|
||||||
// Go on.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostsParser is a helper type to parse rules from the operating system's hosts
|
|
||||||
// file.
|
|
||||||
type hostsParser struct {
|
|
||||||
// rules builds the resulting rules list content.
|
|
||||||
rules *strings.Builder
|
|
||||||
|
|
||||||
// table stores only the unique IP-hostname pairs. It's also sent to the
|
|
||||||
// updates channel afterwards.
|
|
||||||
table *netutil.IPMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
|
||||||
return &hostsParser{
|
|
||||||
rules: &strings.Builder{},
|
|
||||||
table: netutil.NewIPMap(hc.last.Len()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFile is a aghos.FileWalker for parsing the files with hosts syntax. It
|
|
||||||
// never signs to stop walking and never returns any additional patterns.
|
|
||||||
//
|
|
||||||
// See man hosts(5).
|
|
||||||
func (hp *hostsParser) parseFile(
|
|
||||||
r io.Reader,
|
|
||||||
) (patterns []string, cont bool, err error) {
|
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
for s.Scan() {
|
|
||||||
ip, hosts := hp.parseLine(s.Text())
|
|
||||||
if ip == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, host := range hosts {
|
|
||||||
hp.addPair(ip, host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, true, s.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLine parses the line having the hosts syntax ignoring invalid ones.
|
|
||||||
func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip = net.ParseIP(fields[0]); ip == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for _, f := range fields[1:] {
|
|
||||||
switch hashIdx := strings.IndexByte(f, '#'); hashIdx {
|
|
||||||
case 0:
|
|
||||||
// The rest of the fields are a part of the comment so skip
|
|
||||||
// immediately.
|
|
||||||
break loop
|
|
||||||
case -1:
|
|
||||||
hosts = append(hosts, f)
|
|
||||||
default:
|
|
||||||
// Only a part of the field is a comment.
|
|
||||||
hosts = append(hosts, f[:hashIdx])
|
|
||||||
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip, hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
// add returns true if the pair of ip and host wasn't added to the hp before.
|
|
||||||
func (hp *hostsParser) add(ip net.IP, host string) (added bool) {
|
|
||||||
v, ok := hp.table.Get(ip)
|
|
||||||
hosts, _ := v.(*stringutil.Set)
|
|
||||||
switch {
|
|
||||||
case ok && hosts.Has(host):
|
|
||||||
return false
|
|
||||||
case hosts == nil:
|
|
||||||
hosts = stringutil.NewSet(host)
|
|
||||||
hp.table.Set(ip, hosts)
|
|
||||||
default:
|
|
||||||
hosts.Add(host)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// addPair puts the pair of ip and host to the rules builder if needed.
|
|
||||||
func (hp *hostsParser) addPair(ip net.IP, host string) {
|
|
||||||
arpa, err := netutil.IPToReversedAddr(ip)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hp.add(ip, host) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
qtype := "AAAA"
|
|
||||||
if ip.To4() != nil {
|
|
||||||
// Assume the validation of the IP address is performed already.
|
|
||||||
qtype = "A"
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
nl = "\n"
|
|
||||||
sc = ";"
|
|
||||||
|
|
||||||
rewriteSuccess = "$dnsrewrite=NOERROR" + sc
|
|
||||||
rewriteSuccessPTR = rewriteSuccess + "PTR" + sc
|
|
||||||
)
|
|
||||||
|
|
||||||
ipStr := ip.String()
|
|
||||||
fqdn := dns.Fqdn(host)
|
|
||||||
|
|
||||||
for _, ruleData := range [...][]string{{
|
|
||||||
// A/AAAA.
|
|
||||||
rules.MaskStartURL,
|
|
||||||
host,
|
|
||||||
rules.MaskSeparator,
|
|
||||||
rewriteSuccess,
|
|
||||||
qtype,
|
|
||||||
sc,
|
|
||||||
ipStr,
|
|
||||||
nl,
|
|
||||||
}, {
|
|
||||||
// PTR.
|
|
||||||
rules.MaskStartURL,
|
|
||||||
arpa,
|
|
||||||
rules.MaskSeparator,
|
|
||||||
rewriteSuccessPTR,
|
|
||||||
fqdn,
|
|
||||||
nl,
|
|
||||||
}} {
|
|
||||||
stringutil.WriteToBuilder(hp.rules, ruleData...)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("%s: added ip-host pair %q/%q", hostsContainerPref, ip, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalSet returns true if the internal hosts table just parsed equals target.
|
|
||||||
func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
|
||||||
if target == nil {
|
|
||||||
// hp.table shouldn't appear nil since it's initialized on each refresh.
|
|
||||||
return target == hp.table
|
|
||||||
}
|
|
||||||
|
|
||||||
if hp.table.Len() != target.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
hp.table.Range(func(ip net.IP, val interface{}) (cont bool) {
|
|
||||||
v, hasIP := target.Get(ip)
|
|
||||||
// ok is set to true if the target doesn't contain ip or if the
|
|
||||||
// appropriate hosts set isn't equal to the checked one, i.e. the maps
|
|
||||||
// have at least one disperancy.
|
|
||||||
ok = !hasIP || !v.(*stringutil.Set).Equal(val.(*stringutil.Set))
|
|
||||||
|
|
||||||
// Continue only if maps has no discrepancies.
|
|
||||||
return !ok
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return true if every value from the IP map has no disperancies with the
|
|
||||||
// appropriate one from the target.
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendUpd tries to send the parsed data to the ch.
|
|
||||||
func (hp *hostsParser) sendUpd(ch chan *netutil.IPMap) {
|
|
||||||
log.Debug("%s: sending upd", hostsContainerPref)
|
|
||||||
|
|
||||||
upd := hp.table
|
|
||||||
select {
|
|
||||||
case ch <- upd:
|
|
||||||
// Updates are delivered. Go on.
|
|
||||||
case <-ch:
|
|
||||||
ch <- upd
|
|
||||||
log.Debug("%s: replaced the last update", hostsContainerPref)
|
|
||||||
case ch <- upd:
|
|
||||||
// The previous update was just read and the next one pushed. Go on.
|
|
||||||
default:
|
|
||||||
log.Debug("%s: the channel is broken", hostsContainerPref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newStrg creates a new rules storage from parsed data.
|
|
||||||
func (hp *hostsParser) newStrg() (s *filterlist.RuleStorage, err error) {
|
|
||||||
return filterlist.NewRuleStorage([]filterlist.RuleList{&filterlist.StringRuleList{
|
|
||||||
ID: -1,
|
|
||||||
RulesText: hp.rules.String(),
|
|
||||||
IgnoreCosmetic: true,
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh gets the data from specified files and propagates the updates if
|
|
||||||
// needed.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Accept a parameter to specify the files to refresh.
|
|
||||||
func (hc *HostsContainer) refresh() (err error) {
|
|
||||||
log.Debug("%s: refreshing", hostsContainerPref)
|
|
||||||
|
|
||||||
hp := hc.newHostsParser()
|
|
||||||
if _, err = aghos.FileWalker(hp.parseFile).Walk(hc.fsys, hc.patterns...); err != nil {
|
|
||||||
return fmt.Errorf("refreshing : %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hp.equalSet(hc.last) {
|
|
||||||
log.Debug("%s: no updates detected", hostsContainerPref)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer hp.sendUpd(hc.updates)
|
|
||||||
|
|
||||||
hc.last = hp.table.ShallowClone()
|
|
||||||
|
|
||||||
var rulesStrg *filterlist.RuleStorage
|
|
||||||
if rulesStrg, err = hp.newStrg(); err != nil {
|
|
||||||
return fmt.Errorf("initializing rules storage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hc.resetEng(rulesStrg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HostsContainer) resetEng(rulesStrg *filterlist.RuleStorage) {
|
|
||||||
hc.engLock.Lock()
|
|
||||||
defer hc.engLock.Unlock()
|
|
||||||
|
|
||||||
hc.rulesStrg = rulesStrg
|
|
||||||
hc.engine = urlfilter.NewDNSEngine(hc.rulesStrg)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package aghnet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func defaultHostsPaths() (paths []string) {
|
|
||||||
paths = []string{"etc/hosts"}
|
|
||||||
|
|
||||||
if aghos.IsOpenWrt() {
|
|
||||||
paths = append(paths, "tmp/hosts")
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
//go:build !(windows || linux)
|
|
||||||
// +build !windows,!linux
|
|
||||||
|
|
||||||
package aghnet
|
|
||||||
|
|
||||||
func defaultHostsPaths() (paths []string) {
|
|
||||||
return []string{"etc/hosts"}
|
|
||||||
}
|
|
||||||
@@ -1,529 +0,0 @@
|
|||||||
package aghnet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"net"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
|
||||||
"github.com/AdguardTeam/urlfilter"
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nl = "\n"
|
|
||||||
sp = " "
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewHostsContainer(t *testing.T) {
|
|
||||||
const dirname = "dir"
|
|
||||||
const filename = "file1"
|
|
||||||
|
|
||||||
p := path.Join(dirname, filename)
|
|
||||||
|
|
||||||
testFS := fstest.MapFS{
|
|
||||||
p: &fstest.MapFile{Data: []byte("127.0.0.1 localhost")},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
wantErr error
|
|
||||||
name string
|
|
||||||
paths []string
|
|
||||||
}{{
|
|
||||||
wantErr: nil,
|
|
||||||
name: "one_file",
|
|
||||||
paths: []string{p},
|
|
||||||
}, {
|
|
||||||
wantErr: ErrNoHostsPaths,
|
|
||||||
name: "no_files",
|
|
||||||
paths: []string{},
|
|
||||||
}, {
|
|
||||||
wantErr: ErrNoHostsPaths,
|
|
||||||
name: "non-existent_file",
|
|
||||||
paths: []string{path.Join(dirname, filename+"2")},
|
|
||||||
}, {
|
|
||||||
wantErr: nil,
|
|
||||||
name: "whole_dir",
|
|
||||||
paths: []string{dirname},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
onAdd := func(name string) (err error) {
|
|
||||||
assert.Contains(t, tc.paths, name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var eventsCalledCounter uint32
|
|
||||||
eventsCh := make(chan struct{})
|
|
||||||
onEvents := func() (e <-chan struct{}) {
|
|
||||||
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))
|
|
||||||
|
|
||||||
return eventsCh
|
|
||||||
}
|
|
||||||
|
|
||||||
hc, err := NewHostsContainer(testFS, &aghtest.FSWatcher{
|
|
||||||
OnEvents: onEvents,
|
|
||||||
OnAdd: onAdd,
|
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
|
||||||
}, tc.paths...)
|
|
||||||
if tc.wantErr != nil {
|
|
||||||
require.ErrorIs(t, err, tc.wantErr)
|
|
||||||
|
|
||||||
assert.Nil(t, hc)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, hc)
|
|
||||||
|
|
||||||
assert.NotNil(t, <-hc.Upd())
|
|
||||||
|
|
||||||
eventsCh <- struct{}{}
|
|
||||||
assert.Equal(t, uint32(1), atomic.LoadUint32(&eventsCalledCounter))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("nil_fs", func(t *testing.T) {
|
|
||||||
require.Panics(t, func() {
|
|
||||||
_, _ = NewHostsContainer(nil, &aghtest.FSWatcher{
|
|
||||||
// Those shouldn't panic.
|
|
||||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
|
||||||
OnAdd: func(name string) (err error) { return nil },
|
|
||||||
OnClose: func() (err error) { return nil },
|
|
||||||
}, p)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nil_watcher", func(t *testing.T) {
|
|
||||||
require.Panics(t, func() {
|
|
||||||
_, _ = NewHostsContainer(testFS, nil, p)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("err_watcher", func(t *testing.T) {
|
|
||||||
const errOnAdd errors.Error = "error"
|
|
||||||
|
|
||||||
errWatcher := &aghtest.FSWatcher{
|
|
||||||
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
|
|
||||||
OnAdd: func(name string) (err error) { return errOnAdd },
|
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
|
||||||
}
|
|
||||||
|
|
||||||
hc, err := NewHostsContainer(testFS, errWatcher, p)
|
|
||||||
require.ErrorIs(t, err, errOnAdd)
|
|
||||||
|
|
||||||
assert.Nil(t, hc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostsContainer_Refresh(t *testing.T) {
|
|
||||||
knownIP := net.IP{127, 0, 0, 1}
|
|
||||||
|
|
||||||
const knownHost = "localhost"
|
|
||||||
const knownAlias = "hocallost"
|
|
||||||
|
|
||||||
const dirname = "dir"
|
|
||||||
const filename1 = "file1"
|
|
||||||
const filename2 = "file2"
|
|
||||||
|
|
||||||
p1 := path.Join(dirname, filename1)
|
|
||||||
p2 := path.Join(dirname, filename2)
|
|
||||||
|
|
||||||
testFS := fstest.MapFS{
|
|
||||||
p1: &fstest.MapFile{
|
|
||||||
Data: []byte(strings.Join([]string{knownIP.String(), knownHost}, sp) + nl),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// event is a convenient alias for an empty struct{} to emit test events.
|
|
||||||
type event = struct{}
|
|
||||||
|
|
||||||
eventsCh := make(chan event, 1)
|
|
||||||
t.Cleanup(func() { close(eventsCh) })
|
|
||||||
|
|
||||||
w := &aghtest.FSWatcher{
|
|
||||||
OnEvents: func() (e <-chan event) { return eventsCh },
|
|
||||||
OnAdd: func(name string) (err error) {
|
|
||||||
assert.Equal(t, dirname, name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
|
||||||
}
|
|
||||||
|
|
||||||
hc, err := NewHostsContainer(testFS, w, dirname)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
|
|
||||||
upd, ok := <-hc.Upd()
|
|
||||||
require.True(t, ok)
|
|
||||||
require.NotNil(t, upd)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, upd.Len())
|
|
||||||
|
|
||||||
v, ok := upd.Get(knownIP)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
var hosts *stringutil.Set
|
|
||||||
hosts, ok = v.(*stringutil.Set)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
assert.True(t, hosts.Equal(wantHosts))
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("initial_refresh", func(t *testing.T) {
|
|
||||||
checkRefresh(t, stringutil.NewSet(knownHost))
|
|
||||||
})
|
|
||||||
|
|
||||||
testFS[p2] = &fstest.MapFile{
|
|
||||||
Data: []byte(strings.Join([]string{knownIP.String(), knownAlias}, sp) + nl),
|
|
||||||
}
|
|
||||||
eventsCh <- event{}
|
|
||||||
|
|
||||||
t.Run("second_refresh", func(t *testing.T) {
|
|
||||||
checkRefresh(t, stringutil.NewSet(knownHost, knownAlias))
|
|
||||||
})
|
|
||||||
|
|
||||||
eventsCh <- event{}
|
|
||||||
|
|
||||||
t.Run("no_changes_refresh", func(t *testing.T) {
|
|
||||||
assert.Empty(t, hc.Upd())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostsContainer_MatchRequest(t *testing.T) {
|
|
||||||
var (
|
|
||||||
ip4 = net.IP{127, 0, 0, 1}
|
|
||||||
ip6 = net.IP{
|
|
||||||
128, 0, 0, 0,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
0, 0, 0, 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname4 = "localhost"
|
|
||||||
hostname6 = "localhostv6"
|
|
||||||
hostname4a = "abcd"
|
|
||||||
|
|
||||||
reversed4, _ = netutil.IPToReversedAddr(ip4)
|
|
||||||
reversed6, _ = netutil.IPToReversedAddr(ip6)
|
|
||||||
)
|
|
||||||
|
|
||||||
const filename = "file1"
|
|
||||||
|
|
||||||
gsfs := fstest.MapFS{
|
|
||||||
filename: &fstest.MapFile{Data: []byte(
|
|
||||||
ip4.String() + " " + hostname4 + " " + hostname4a + nl +
|
|
||||||
ip6.String() + " " + hostname6 + nl +
|
|
||||||
`256.256.256.256 fakebroadcast` + nl,
|
|
||||||
)},
|
|
||||||
}
|
|
||||||
|
|
||||||
hc, err := NewHostsContainer(gsfs, &aghtest.FSWatcher{
|
|
||||||
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
|
|
||||||
OnAdd: func(name string) (err error) {
|
|
||||||
assert.Equal(t, filename, name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
|
||||||
}, filename)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testCase := []struct {
|
|
||||||
name string
|
|
||||||
want []interface{}
|
|
||||||
req urlfilter.DNSRequest
|
|
||||||
}{{
|
|
||||||
name: "a",
|
|
||||||
want: []interface{}{ip4.To16()},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: hostname4,
|
|
||||||
DNSType: dns.TypeA,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "a_for_aaaa",
|
|
||||||
want: []interface{}{
|
|
||||||
ip4.To16(),
|
|
||||||
},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: hostname4,
|
|
||||||
DNSType: dns.TypeAAAA,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "aaaa",
|
|
||||||
want: []interface{}{ip6},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: hostname6,
|
|
||||||
DNSType: dns.TypeAAAA,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "ptr",
|
|
||||||
want: []interface{}{
|
|
||||||
dns.Fqdn(hostname4),
|
|
||||||
dns.Fqdn(hostname4a),
|
|
||||||
},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: reversed4,
|
|
||||||
DNSType: dns.TypePTR,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "ptr_v6",
|
|
||||||
want: []interface{}{dns.Fqdn(hostname6)},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: reversed6,
|
|
||||||
DNSType: dns.TypePTR,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "a_alias",
|
|
||||||
want: []interface{}{ip4.To16()},
|
|
||||||
req: urlfilter.DNSRequest{
|
|
||||||
Hostname: hostname4a,
|
|
||||||
DNSType: dns.TypeA,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCase {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
res, ok := hc.MatchRequest(tc.req)
|
|
||||||
require.False(t, ok)
|
|
||||||
require.NotNil(t, res)
|
|
||||||
|
|
||||||
rws := res.DNSRewrites()
|
|
||||||
require.Len(t, rws, len(tc.want))
|
|
||||||
|
|
||||||
for i, w := range tc.want {
|
|
||||||
require.NotNil(t, rws[i])
|
|
||||||
|
|
||||||
rw := rws[i].DNSRewrite
|
|
||||||
require.NotNil(t, rw)
|
|
||||||
|
|
||||||
assert.Equal(t, w, rw.Value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("cname", func(t *testing.T) {
|
|
||||||
res, ok := hc.MatchRequest(urlfilter.DNSRequest{
|
|
||||||
Hostname: hostname4,
|
|
||||||
DNSType: dns.TypeCNAME,
|
|
||||||
})
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
assert.Nil(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostsContainer_PathsToPatterns(t *testing.T) {
|
|
||||||
const (
|
|
||||||
dir0 = "dir"
|
|
||||||
dir1 = "dir_1"
|
|
||||||
fn1 = "file_1"
|
|
||||||
fn2 = "file_2"
|
|
||||||
fn3 = "file_3"
|
|
||||||
fn4 = "file_4"
|
|
||||||
)
|
|
||||||
|
|
||||||
fp1 := path.Join(dir0, fn1)
|
|
||||||
fp2 := path.Join(dir0, fn2)
|
|
||||||
fp3 := path.Join(dir0, dir1, fn3)
|
|
||||||
|
|
||||||
gsfs := fstest.MapFS{
|
|
||||||
fp1: &fstest.MapFile{Data: []byte{1}},
|
|
||||||
fp2: &fstest.MapFile{Data: []byte{2}},
|
|
||||||
fp3: &fstest.MapFile{Data: []byte{3}},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
wantErr error
|
|
||||||
want []string
|
|
||||||
paths []string
|
|
||||||
}{{
|
|
||||||
name: "no_paths",
|
|
||||||
wantErr: nil,
|
|
||||||
want: nil,
|
|
||||||
paths: nil,
|
|
||||||
}, {
|
|
||||||
name: "single_file",
|
|
||||||
wantErr: nil,
|
|
||||||
want: []string{fp1},
|
|
||||||
paths: []string{fp1},
|
|
||||||
}, {
|
|
||||||
name: "several_files",
|
|
||||||
wantErr: nil,
|
|
||||||
want: []string{fp1, fp2},
|
|
||||||
paths: []string{fp1, fp2},
|
|
||||||
}, {
|
|
||||||
name: "whole_dir",
|
|
||||||
wantErr: nil,
|
|
||||||
want: []string{path.Join(dir0, "*")},
|
|
||||||
paths: []string{dir0},
|
|
||||||
}, {
|
|
||||||
name: "file_and_dir",
|
|
||||||
wantErr: nil,
|
|
||||||
want: []string{fp1, path.Join(dir0, dir1, "*")},
|
|
||||||
paths: []string{fp1, path.Join(dir0, dir1)},
|
|
||||||
}, {
|
|
||||||
name: "non-existing",
|
|
||||||
wantErr: nil,
|
|
||||||
want: nil,
|
|
||||||
paths: []string{path.Join(dir0, "file_3")},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
patterns, err := pathsToPatterns(gsfs, tc.paths)
|
|
||||||
if tc.wantErr != nil {
|
|
||||||
assert.ErrorIs(t, err, tc.wantErr)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, tc.want, patterns)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("bad_file", func(t *testing.T) {
|
|
||||||
const errStat errors.Error = "bad file"
|
|
||||||
|
|
||||||
badFS := &aghtest.StatFS{
|
|
||||||
OnStat: func(name string) (fs.FileInfo, error) {
|
|
||||||
return nil, errStat
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := pathsToPatterns(badFS, []string{""})
|
|
||||||
assert.ErrorIs(t, err, errStat)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUniqueRules_AddPair(t *testing.T) {
|
|
||||||
knownIP := net.IP{1, 2, 3, 4}
|
|
||||||
|
|
||||||
const knownHost = "host1"
|
|
||||||
|
|
||||||
ipToHost := netutil.NewIPMap(0)
|
|
||||||
ipToHost.Set(knownIP, *stringutil.NewSet(knownHost))
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
host string
|
|
||||||
wantRules string
|
|
||||||
ip net.IP
|
|
||||||
}{{
|
|
||||||
name: "new_one",
|
|
||||||
host: "host2",
|
|
||||||
wantRules: "||host2^$dnsrewrite=NOERROR;A;1.2.3.4\n" +
|
|
||||||
"||4.3.2.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;host2.\n",
|
|
||||||
ip: knownIP,
|
|
||||||
}, {
|
|
||||||
name: "existing_one",
|
|
||||||
host: knownHost,
|
|
||||||
wantRules: "||" + knownHost + "^$dnsrewrite=NOERROR;A;1.2.3.4\n" +
|
|
||||||
"||4.3.2.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;host1.\n",
|
|
||||||
ip: knownIP,
|
|
||||||
}, {
|
|
||||||
name: "new_ip",
|
|
||||||
host: knownHost,
|
|
||||||
wantRules: "||" + knownHost + "^$dnsrewrite=NOERROR;A;1.2.3.5\n" +
|
|
||||||
"||5.3.2.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;" + knownHost + ".\n",
|
|
||||||
ip: net.IP{1, 2, 3, 5},
|
|
||||||
}, {
|
|
||||||
name: "bad_ip",
|
|
||||||
host: knownHost,
|
|
||||||
wantRules: "",
|
|
||||||
ip: net.IP{1, 2, 3, 4, 5},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
hp := hostsParser{
|
|
||||||
rules: &strings.Builder{},
|
|
||||||
table: ipToHost.ShallowClone(),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
hp.addPair(tc.ip, tc.host)
|
|
||||||
assert.Equal(t, tc.wantRules, hp.rules.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUniqueRules_ParseLine(t *testing.T) {
|
|
||||||
const (
|
|
||||||
hostname = "localhost"
|
|
||||||
alias = "hocallost"
|
|
||||||
)
|
|
||||||
|
|
||||||
knownIP := net.IP{127, 0, 0, 1}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
line string
|
|
||||||
wantIP net.IP
|
|
||||||
wantHosts []string
|
|
||||||
}{{
|
|
||||||
name: "simple",
|
|
||||||
line: strings.Join([]string{knownIP.String(), hostname}, sp),
|
|
||||||
wantIP: knownIP,
|
|
||||||
wantHosts: []string{"localhost"},
|
|
||||||
}, {
|
|
||||||
name: "aliases",
|
|
||||||
line: strings.Join([]string{knownIP.String(), hostname, alias}, sp),
|
|
||||||
wantIP: knownIP,
|
|
||||||
wantHosts: []string{"localhost", "hocallost"},
|
|
||||||
}, {
|
|
||||||
name: "invalid_line",
|
|
||||||
line: knownIP.String(),
|
|
||||||
wantIP: nil,
|
|
||||||
wantHosts: nil,
|
|
||||||
}, {
|
|
||||||
name: "invalid_line_hostname",
|
|
||||||
line: strings.Join([]string{knownIP.String(), "#" + hostname}, sp),
|
|
||||||
wantIP: knownIP,
|
|
||||||
wantHosts: nil,
|
|
||||||
}, {
|
|
||||||
name: "commented_aliases",
|
|
||||||
line: strings.Join([]string{knownIP.String(), hostname, "#" + alias}, sp),
|
|
||||||
wantIP: knownIP,
|
|
||||||
wantHosts: []string{"localhost"},
|
|
||||||
}, {
|
|
||||||
name: "whole_comment",
|
|
||||||
line: strings.Join([]string{"#", knownIP.String(), hostname}, sp),
|
|
||||||
wantIP: nil,
|
|
||||||
wantHosts: nil,
|
|
||||||
}, {
|
|
||||||
name: "partial_comment",
|
|
||||||
line: strings.Join([]string{knownIP.String(), hostname[:4] + "#" + hostname[4:]}, sp),
|
|
||||||
wantIP: knownIP,
|
|
||||||
wantHosts: []string{hostname[:4]},
|
|
||||||
}, {
|
|
||||||
name: "empty",
|
|
||||||
line: ``,
|
|
||||||
wantIP: nil,
|
|
||||||
wantHosts: nil,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
hp := hostsParser{}
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
ip, hosts := hp.parseLine(tc.line)
|
|
||||||
assert.True(t, tc.wantIP.Equal(ip))
|
|
||||||
assert.Equal(t, tc.wantHosts, hosts)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package aghnet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func defaultHostsPaths() (paths []string) {
|
|
||||||
sysDir, err := windows.GetSystemDirectory()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("getting system directory: %s", err)
|
|
||||||
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split all the elements of the path to join them afterwards. This is
|
|
||||||
// needed to make the Windows-specific path string returned by
|
|
||||||
// windows.GetSystemDirectory to be compatible with fs.FS.
|
|
||||||
pathElems := strings.Split(sysDir, string(os.PathSeparator))
|
|
||||||
if len(pathElems) > 0 && pathElems[0] == filepath.VolumeName(sysDir) {
|
|
||||||
pathElems = pathElems[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{path.Join(append(pathElems, "drivers/etc/hosts")...)}
|
|
||||||
}
|
|
||||||
@@ -18,11 +18,9 @@ func canBindPrivilegedPorts() (can bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
||||||
const rcConfFilename = "etc/rc.conf"
|
const filename = "/etc/rc.conf"
|
||||||
|
|
||||||
walker := aghos.FileWalker(interfaceName(ifaceName).rcConfStaticConfig)
|
return aghos.FileWalker(interfaceName(ifaceName).rcConfStaticConfig).Walk(filename)
|
||||||
|
|
||||||
return walker.Walk(aghos.RootDirFS(), rcConfFilename)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rcConfStaticConfig checks if the interface is configured by /etc/rc.conf to
|
// rcConfStaticConfig checks if the interface is configured by /etc/rc.conf to
|
||||||
|
|||||||
@@ -85,17 +85,17 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
|||||||
|
|
||||||
iface := interfaceName(ifaceName)
|
iface := interfaceName(ifaceName)
|
||||||
|
|
||||||
for _, pair := range [...]struct {
|
for _, pair := range []struct {
|
||||||
aghos.FileWalker
|
aghos.FileWalker
|
||||||
filename string
|
filename string
|
||||||
}{{
|
}{{
|
||||||
FileWalker: iface.dhcpcdStaticConfig,
|
FileWalker: iface.dhcpcdStaticConfig,
|
||||||
filename: "etc/dhcpcd.conf",
|
filename: "/etc/dhcpcd.conf",
|
||||||
}, {
|
}, {
|
||||||
FileWalker: iface.ifacesStaticConfig,
|
FileWalker: iface.ifacesStaticConfig,
|
||||||
filename: "etc/network/interfaces",
|
filename: "/etc/network/interfaces",
|
||||||
}} {
|
}} {
|
||||||
has, err = pair.Walk(aghos.RootDirFS(), pair.filename)
|
has, err = pair.Walk(pair.filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const nl = "\n"
|
||||||
|
|
||||||
func TestDHCPCDStaticConfig(t *testing.T) {
|
func TestDHCPCDStaticConfig(t *testing.T) {
|
||||||
const iface interfaceName = `wlan0`
|
const iface interfaceName = `wlan0`
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ func canBindPrivilegedPorts() (can bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
|
||||||
filename := fmt.Sprintf("etc/hostname.%s", ifaceName)
|
filename := fmt.Sprintf("/etc/hostname.%s", ifaceName)
|
||||||
|
|
||||||
return aghos.FileWalker(hostnameIfStaticConfig).Walk(aghos.RootDirFS(), filename)
|
return aghos.FileWalker(hostnameIfStaticConfig).Walk(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hostnameIfStaticConfig checks if the interface is configured by
|
// hostnameIfStaticConfig checks if the interface is configured by
|
||||||
|
|||||||
@@ -4,15 +4,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
aghtest.DiscardLogOutput(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
||||||
ifaces, err := GetValidNetInterfacesForWeb()
|
ifaces, err := GetValidNetInterfacesForWeb()
|
||||||
require.NoErrorf(t, err, "cannot get net interfaces: %s", err)
|
require.NoErrorf(t, err, "cannot get net interfaces: %s", err)
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ func TestSystemResolvers_DialFunc(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) {
|
||||||
conn, err := imp.dialFunc(context.Background(), "", tc.address)
|
conn, err := imp.dialFunc(context.Background(), "", tc.address)
|
||||||
require.Nil(t, conn)
|
|
||||||
|
|
||||||
|
require.Nil(t, conn)
|
||||||
assert.ErrorIs(t, err, tc.want)
|
assert.ErrorIs(t, err, tc.want)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package aghos
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
@@ -13,10 +14,10 @@ import (
|
|||||||
// FileWalker is the signature of a function called for files in the file tree.
|
// FileWalker is the signature of a function called for files in the file tree.
|
||||||
// As opposed to filepath.Walk it only walk the files (not directories) matching
|
// As opposed to filepath.Walk it only walk the files (not directories) matching
|
||||||
// the provided pattern and those returned by function itself. All patterns
|
// the provided pattern and those returned by function itself. All patterns
|
||||||
// should be valid for fs.Glob. If cont is false, the walking terminates. Each
|
// should be valid for filepath.Glob. If cont is false, the walking terminates.
|
||||||
// opened file is also limited for reading to MaxWalkedFileSize.
|
// Each opened file is also limited for reading to MaxWalkedFileSize.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
|
// TODO(e.burkov): Consider moving to the separate package like pathutil.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Think about passing filename or any additional data.
|
// TODO(e.burkov): Think about passing filename or any additional data.
|
||||||
type FileWalker func(r io.Reader) (patterns []string, cont bool, err error)
|
type FileWalker func(r io.Reader) (patterns []string, cont bool, err error)
|
||||||
@@ -25,19 +26,15 @@ type FileWalker func(r io.Reader) (patterns []string, cont bool, err error)
|
|||||||
// check.
|
// check.
|
||||||
const MaxWalkedFileSize = 1024 * 1024
|
const MaxWalkedFileSize = 1024 * 1024
|
||||||
|
|
||||||
// checkFile tries to open and process a single file located on sourcePath in
|
// checkFile tries to open and process a single file located on sourcePath.
|
||||||
// the specified fsys. The path is skipped if it's a directory.
|
func checkFile(c FileWalker, sourcePath string) (patterns []string, cont bool, err error) {
|
||||||
func checkFile(
|
var f *os.File
|
||||||
fsys fs.FS,
|
f, err = os.Open(sourcePath)
|
||||||
c FileWalker,
|
|
||||||
sourcePath string,
|
|
||||||
) (patterns []string, cont bool, err error) {
|
|
||||||
var f fs.File
|
|
||||||
f, err = fsys.Open(sourcePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// Ignore non-existing files since this may only happen when the
|
// Ignore non-existing files since this may only happen
|
||||||
// file was removed after filepath.Glob matched it.
|
// when the file was removed after filepath.Glob matched
|
||||||
|
// it.
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,18 +42,9 @@ func checkFile(
|
|||||||
}
|
}
|
||||||
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
||||||
|
|
||||||
var fi fs.FileInfo
|
|
||||||
if fi, err = f.Stat(); err != nil {
|
|
||||||
return nil, true, err
|
|
||||||
}
|
|
||||||
if fi.IsDir() {
|
|
||||||
// Skip the directories.
|
|
||||||
return nil, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
// Ignore the error since LimitReader function returns error only if passed
|
// Ignore the error since LimitReader function returns error only if
|
||||||
// limit value is less than zero, but the constant used.
|
// passed limit value is less than zero, but the constant used.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Make variable.
|
// TODO(e.burkov): Make variable.
|
||||||
r, _ = aghio.LimitReader(f, MaxWalkedFileSize)
|
r, _ = aghio.LimitReader(f, MaxWalkedFileSize)
|
||||||
@@ -64,17 +52,13 @@ func checkFile(
|
|||||||
return c(r)
|
return c(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePatterns parses the patterns in fsys and ignores duplicates using
|
// handlePatterns parses the patterns and ignores duplicates using srcSet.
|
||||||
// srcSet. srcSet must be non-nil.
|
// srcSet must be non-nil.
|
||||||
func handlePatterns(
|
func handlePatterns(srcSet *stringutil.Set, patterns ...string) (sub []string, err error) {
|
||||||
fsys fs.FS,
|
|
||||||
srcSet *stringutil.Set,
|
|
||||||
patterns ...string,
|
|
||||||
) (sub []string, err error) {
|
|
||||||
sub = make([]string, 0, len(patterns))
|
sub = make([]string, 0, len(patterns))
|
||||||
for _, p := range patterns {
|
for _, p := range patterns {
|
||||||
var matches []string
|
var matches []string
|
||||||
matches, err = fs.Glob(fsys, p)
|
matches, err = filepath.Glob(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Enrich error with the pattern because filepath.Glob
|
// Enrich error with the pattern because filepath.Glob
|
||||||
// doesn't do it.
|
// doesn't do it.
|
||||||
@@ -94,14 +78,14 @@ func handlePatterns(
|
|||||||
return sub, nil
|
return sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk starts walking the files in fsys defined by patterns from initial.
|
// Walk starts walking the files defined by initPattern. It only returns true
|
||||||
// It only returns true if fw signed to stop walking.
|
// if c signed to stop walking.
|
||||||
func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
func (c FileWalker) Walk(initPattern string) (ok bool, err error) {
|
||||||
// The slice of sources keeps the order in which the files are walked since
|
// The slice of sources keeps the order in which the files are walked
|
||||||
// srcSet.Values() returns strings in undefined order.
|
// since srcSet.Values() returns strings in undefined order.
|
||||||
srcSet := stringutil.NewSet()
|
srcSet := stringutil.NewSet()
|
||||||
var src []string
|
var src []string
|
||||||
src, err = handlePatterns(fsys, srcSet, initial...)
|
src, err = handlePatterns(srcSet, initPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -113,7 +97,7 @@ func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
|||||||
var patterns []string
|
var patterns []string
|
||||||
var cont bool
|
var cont bool
|
||||||
filename = src[i]
|
filename = src[i]
|
||||||
patterns, cont, err = checkFile(fsys, fw, src[i])
|
patterns, cont, err = checkFile(c, src[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -123,7 +107,7 @@ func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var subsrc []string
|
var subsrc []string
|
||||||
subsrc, err = handlePatterns(fsys, srcSet, patterns...)
|
subsrc, err = handlePatterns(srcSet, patterns...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,56 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// testFSDir maps entries' names to entries which should either be a testFSDir
|
||||||
|
// or byte slice.
|
||||||
|
type testFSDir map[string]interface{}
|
||||||
|
|
||||||
|
// testFSGen is used to generate a temporary filesystem consisting of
|
||||||
|
// directories and plain text files from itself.
|
||||||
|
type testFSGen testFSDir
|
||||||
|
|
||||||
|
// gen returns the name of top directory of the generated filesystem.
|
||||||
|
func (g testFSGen) gen(t *testing.T) (dirName string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
dirName = t.TempDir()
|
||||||
|
g.rangeThrough(t, dirName)
|
||||||
|
|
||||||
|
return dirName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g testFSGen) rangeThrough(t *testing.T, dirName string) {
|
||||||
|
const perm fs.FileMode = 0o777
|
||||||
|
|
||||||
|
for k, e := range g {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case []byte:
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(dirName, k), e, perm))
|
||||||
|
|
||||||
|
case testFSDir:
|
||||||
|
newDir := filepath.Join(dirName, k)
|
||||||
|
require.NoError(t, os.Mkdir(newDir, perm))
|
||||||
|
|
||||||
|
testFSGen(e).rangeThrough(t, newDir)
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected entry type %T", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileWalker_Walk(t *testing.T) {
|
func TestFileWalker_Walk(t *testing.T) {
|
||||||
const attribute = `000`
|
const attribute = `000`
|
||||||
|
|
||||||
makeFileWalker := func(_ string) (fw FileWalker) {
|
makeFileWalker := func(dirName string) (fw FileWalker) {
|
||||||
return func(r io.Reader) (patterns []string, cont bool, err error) {
|
return func(r io.Reader) (patterns []string, cont bool, err error) {
|
||||||
s := bufio.NewScanner(r)
|
s := bufio.NewScanner(r)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
@@ -26,7 +63,7 @@ func TestFileWalker_Walk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(line) != 0 {
|
if len(line) != 0 {
|
||||||
patterns = append(patterns, path.Join(".", line))
|
patterns = append(patterns, filepath.Join(dirName, line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,150 +74,136 @@ func TestFileWalker_Walk(t *testing.T) {
|
|||||||
const nl = "\n"
|
const nl = "\n"
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
testFS fstest.MapFS
|
|
||||||
want assert.BoolAssertionFunc
|
|
||||||
initPattern string
|
|
||||||
name string
|
name string
|
||||||
|
testFS testFSGen
|
||||||
|
initPattern string
|
||||||
|
want bool
|
||||||
}{{
|
}{{
|
||||||
name: "simple",
|
name: "simple",
|
||||||
testFS: fstest.MapFS{
|
testFS: testFSGen{
|
||||||
"simple_0001.txt": &fstest.MapFile{Data: []byte(attribute + nl)},
|
"simple_0001.txt": []byte(attribute + nl),
|
||||||
},
|
},
|
||||||
initPattern: "simple_0001.txt",
|
initPattern: "simple_0001.txt",
|
||||||
want: assert.True,
|
want: true,
|
||||||
}, {
|
}, {
|
||||||
name: "chain",
|
name: "chain",
|
||||||
testFS: fstest.MapFS{
|
testFS: testFSGen{
|
||||||
"chain_0001.txt": &fstest.MapFile{Data: []byte(`chain_0002.txt` + nl)},
|
"chain_0001.txt": []byte(`chain_0002.txt` + nl),
|
||||||
"chain_0002.txt": &fstest.MapFile{Data: []byte(`chain_0003.txt` + nl)},
|
"chain_0002.txt": []byte(`chain_0003.txt` + nl),
|
||||||
"chain_0003.txt": &fstest.MapFile{Data: []byte(attribute + nl)},
|
"chain_0003.txt": []byte(attribute + nl),
|
||||||
},
|
},
|
||||||
initPattern: "chain_0001.txt",
|
initPattern: "chain_0001.txt",
|
||||||
want: assert.True,
|
want: true,
|
||||||
}, {
|
}, {
|
||||||
name: "several",
|
name: "several",
|
||||||
testFS: fstest.MapFS{
|
testFS: testFSGen{
|
||||||
"several_0001.txt": &fstest.MapFile{Data: []byte(`several_*` + nl)},
|
"several_0001.txt": []byte(`several_*` + nl),
|
||||||
"several_0002.txt": &fstest.MapFile{Data: []byte(`several_0001.txt` + nl)},
|
"several_0002.txt": []byte(`several_0001.txt` + nl),
|
||||||
"several_0003.txt": &fstest.MapFile{Data: []byte(attribute + nl)},
|
"several_0003.txt": []byte(attribute + nl),
|
||||||
},
|
},
|
||||||
initPattern: "several_0001.txt",
|
initPattern: "several_0001.txt",
|
||||||
want: assert.True,
|
want: true,
|
||||||
}, {
|
}, {
|
||||||
name: "no",
|
name: "no",
|
||||||
testFS: fstest.MapFS{
|
testFS: testFSGen{
|
||||||
"no_0001.txt": &fstest.MapFile{Data: []byte(nl)},
|
"no_0001.txt": []byte(nl),
|
||||||
"no_0002.txt": &fstest.MapFile{Data: []byte(nl)},
|
"no_0002.txt": []byte(nl),
|
||||||
"no_0003.txt": &fstest.MapFile{Data: []byte(nl)},
|
"no_0003.txt": []byte(nl),
|
||||||
},
|
},
|
||||||
initPattern: "no_*",
|
initPattern: "no_*",
|
||||||
want: assert.False,
|
want: false,
|
||||||
}, {
|
}, {
|
||||||
name: "subdirectory",
|
name: "subdirectory",
|
||||||
testFS: fstest.MapFS{
|
testFS: testFSGen{
|
||||||
path.Join("dir", "subdir_0002.txt"): &fstest.MapFile{
|
"dir": testFSDir{
|
||||||
Data: []byte(attribute + nl),
|
"subdir_0002.txt": []byte(attribute + nl),
|
||||||
},
|
},
|
||||||
"subdir_0001.txt": &fstest.MapFile{Data: []byte(`dir/*`)},
|
"subdir_0001.txt": []byte(`dir/*`),
|
||||||
},
|
},
|
||||||
initPattern: "subdir_0001.txt",
|
initPattern: "subdir_0001.txt",
|
||||||
want: assert.True,
|
want: true,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
fw := makeFileWalker("")
|
testDir := tc.testFS.gen(t)
|
||||||
|
fw := makeFileWalker(testDir)
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ok, err := fw.Walk(tc.testFS, tc.initPattern)
|
ok, err := fw.Walk(filepath.Join(testDir, tc.initPattern))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tc.want(t, ok)
|
assert.Equal(t, tc.want, ok)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("pattern_malformed", func(t *testing.T) {
|
t.Run("pattern_malformed", func(t *testing.T) {
|
||||||
f := fstest.MapFS{}
|
ok, err := makeFileWalker("").Walk("[]")
|
||||||
ok, err := makeFileWalker("").Walk(f, "[]")
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.ErrorIs(t, err, path.ErrBadPattern)
|
assert.ErrorIs(t, err, filepath.ErrBadPattern)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad_filename", func(t *testing.T) {
|
t.Run("bad_filename", func(t *testing.T) {
|
||||||
const filename = "bad_filename.txt"
|
dir := testFSGen{
|
||||||
|
"bad_filename.txt": []byte("[]"),
|
||||||
f := fstest.MapFS{
|
}.gen(t)
|
||||||
filename: &fstest.MapFile{Data: []byte("[]")},
|
fw := FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
|
||||||
}
|
|
||||||
ok, err := FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
|
|
||||||
s := bufio.NewScanner(r)
|
s := bufio.NewScanner(r)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
patterns = append(patterns, s.Text())
|
patterns = append(patterns, s.Text())
|
||||||
}
|
}
|
||||||
|
|
||||||
return patterns, true, s.Err()
|
return patterns, true, s.Err()
|
||||||
}).Walk(f, filename)
|
})
|
||||||
|
|
||||||
|
ok, err := fw.Walk(filepath.Join(dir, "bad_filename.txt"))
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.ErrorIs(t, err, path.ErrBadPattern)
|
assert.ErrorIs(t, err, filepath.ErrBadPattern)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("itself_error", func(t *testing.T) {
|
t.Run("itself_error", func(t *testing.T) {
|
||||||
const rerr errors.Error = "returned error"
|
const rerr errors.Error = "returned error"
|
||||||
|
|
||||||
f := fstest.MapFS{
|
dir := testFSGen{
|
||||||
"mockfile.txt": &fstest.MapFile{Data: []byte(`mockdata`)},
|
"mockfile.txt": []byte(`mockdata`),
|
||||||
}
|
}.gen(t)
|
||||||
|
|
||||||
ok, err := FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) {
|
ok, err := FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) {
|
||||||
return nil, true, rerr
|
return nil, true, rerr
|
||||||
}).Walk(f, "*")
|
}).Walk(filepath.Join(dir, "*"))
|
||||||
require.ErrorIs(t, err, rerr)
|
require.Error(t, err)
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
assert.False(t, ok)
|
assert.ErrorIs(t, err, rerr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type errFS struct {
|
|
||||||
fs.GlobFS
|
|
||||||
}
|
|
||||||
|
|
||||||
const errErrFSOpen errors.Error = "this error is always returned"
|
|
||||||
|
|
||||||
func (efs *errFS) Open(name string) (fs.File, error) {
|
|
||||||
return nil, errErrFSOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWalkerFunc_CheckFile(t *testing.T) {
|
func TestWalkerFunc_CheckFile(t *testing.T) {
|
||||||
emptyFS := fstest.MapFS{}
|
|
||||||
|
|
||||||
t.Run("non-existing", func(t *testing.T) {
|
t.Run("non-existing", func(t *testing.T) {
|
||||||
_, ok, err := checkFile(emptyFS, nil, "lol")
|
_, ok, err := checkFile(nil, "lol")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid_argument", func(t *testing.T) {
|
t.Run("invalid_argument", func(t *testing.T) {
|
||||||
_, ok, err := checkFile(&errFS{}, nil, "")
|
const badPath = "\x00"
|
||||||
require.ErrorIs(t, err, errErrFSOpen)
|
|
||||||
|
_, ok, err := checkFile(nil, badPath)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
})
|
// TODO(e.burkov): Use assert.ErrorsIs within the error from
|
||||||
|
// less platform-dependent package instead of syscall.EINVAL.
|
||||||
t.Run("ignore_dirs", func(t *testing.T) {
|
//
|
||||||
const dirName = "dir"
|
// See https://github.com/golang/go/issues/46849 and
|
||||||
|
// https://github.com/golang/go/issues/30322.
|
||||||
testFS := fstest.MapFS{
|
pathErr := &os.PathError{}
|
||||||
path.Join(dirName, "file"): &fstest.MapFile{Data: []byte{}},
|
require.ErrorAs(t, err, &pathErr)
|
||||||
}
|
assert.Equal(t, "open", pathErr.Op)
|
||||||
|
assert.Equal(t, badPath, pathErr.Path)
|
||||||
patterns, ok, err := checkFile(testFS, nil, dirName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Empty(t, patterns)
|
|
||||||
assert.True(t, ok)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
package aghos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
)
|
|
||||||
|
|
||||||
// event is a convenient alias for an empty struct to signal that watching
|
|
||||||
// event happened.
|
|
||||||
type event = struct{}
|
|
||||||
|
|
||||||
// FSWatcher tracks all the fyle system events and notifies about those.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
|
|
||||||
type FSWatcher interface {
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// Events should return a read-only channel which notifies about events.
|
|
||||||
Events() (e <-chan event)
|
|
||||||
|
|
||||||
// Add should check if the file named name is accessible and starts tracking
|
|
||||||
// it.
|
|
||||||
Add(name string) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// osWatcher tracks the file system provided by the OS.
|
|
||||||
type osWatcher struct {
|
|
||||||
// w is the actual notifier that is handled by osWatcher.
|
|
||||||
w *fsnotify.Watcher
|
|
||||||
|
|
||||||
// events is the channel to notify.
|
|
||||||
events chan event
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
|
|
||||||
// methods.
|
|
||||||
osWatcherPref = "os watcher"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewOSWritesWatcher creates FSWatcher that tracks the real file system of the
|
|
||||||
// OS and notifies only about writing events.
|
|
||||||
func NewOSWritesWatcher() (w FSWatcher, err error) {
|
|
||||||
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
|
|
||||||
|
|
||||||
var watcher *fsnotify.Watcher
|
|
||||||
watcher, err = fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating watcher: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fsw := &osWatcher{
|
|
||||||
w: watcher,
|
|
||||||
events: make(chan event, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
go fsw.handleErrors()
|
|
||||||
go fsw.handleEvents()
|
|
||||||
|
|
||||||
return fsw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleErrors handles accompanying errors. It used to be called in a separate
|
|
||||||
// goroutine.
|
|
||||||
func (w *osWatcher) handleErrors() {
|
|
||||||
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
|
|
||||||
|
|
||||||
for err := range w.w.Errors {
|
|
||||||
log.Error("%s: %s", osWatcherPref, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events implements the FSWatcher interface for *osWatcher.
|
|
||||||
func (w *osWatcher) Events() (e <-chan event) {
|
|
||||||
return w.events
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add implements the FSWatcher interface for *osWatcher.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Make it accept non-existing files to detect it's creating.
|
|
||||||
func (w *osWatcher) Add(name string) (err error) {
|
|
||||||
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
|
|
||||||
|
|
||||||
if _, err = fs.Stat(RootDirFS(), name); err != nil {
|
|
||||||
return fmt.Errorf("checking file %q: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.w.Add(filepath.Join("/", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the FSWatcher interface for *osWatcher.
|
|
||||||
func (w *osWatcher) Close() (err error) {
|
|
||||||
return w.w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEvents notifies about the received file system's event if needed. It
|
|
||||||
// used to be called in a separate goroutine.
|
|
||||||
func (w *osWatcher) handleEvents() {
|
|
||||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", osWatcherPref))
|
|
||||||
|
|
||||||
defer close(w.events)
|
|
||||||
|
|
||||||
ch := w.w.Events
|
|
||||||
for e := range ch {
|
|
||||||
if e.Op&fsnotify.Write == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the following events assuming that sometimes the same event
|
|
||||||
// occurrs several times.
|
|
||||||
for ok := true; ok; {
|
|
||||||
select {
|
|
||||||
case _, ok = <-ch:
|
|
||||||
// Go on.
|
|
||||||
default:
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case w.events <- event{}:
|
|
||||||
// Go on.
|
|
||||||
default:
|
|
||||||
log.Debug("%s: events buffer is full", osWatcherPref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -161,10 +159,3 @@ ScanLoop:
|
|||||||
func IsOpenWrt() (ok bool) {
|
func IsOpenWrt() (ok bool) {
|
||||||
return isOpenWrt()
|
return isOpenWrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootDirFS returns the fs.FS rooted at the operating system's root.
|
|
||||||
func RootDirFS() (fsys fs.FS) {
|
|
||||||
// Use empty string since os.DirFS implicitly prepends a slash to it. This
|
|
||||||
// behavior is undocumented but it currently works.
|
|
||||||
return os.DirFS("")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ func haveAdminRights() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isOpenWrt() (ok bool) {
|
func isOpenWrt() (ok bool) {
|
||||||
const etcReleasePattern = "etc/*release*"
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ok, err = FileWalker(func(r io.Reader) (_ []string, cont bool, err error) {
|
ok, err = FileWalker(func(r io.Reader) (_ []string, cont bool, err error) {
|
||||||
const osNameData = "openwrt"
|
const osNameData = "openwrt"
|
||||||
@@ -41,7 +39,7 @@ func isOpenWrt() (ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil, !stringutil.ContainsFold(string(data), osNameData), nil
|
return nil, !stringutil.ContainsFold(string(data), osNameData), nil
|
||||||
}).Walk(RootDirFS(), etcReleasePattern)
|
}).Walk("/etc/*release*")
|
||||||
|
|
||||||
return err == nil && ok
|
return err == nil && ok
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package aghtest
|
|
||||||
|
|
||||||
// FSWatcher is a mock aghos.FSWatcher implementation to use in tests.
|
|
||||||
type FSWatcher struct {
|
|
||||||
OnEvents func() (e <-chan struct{})
|
|
||||||
OnAdd func(name string) (err error)
|
|
||||||
OnClose func() (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events implements the aghos.FSWatcher interface for *FSWatcher.
|
|
||||||
func (w *FSWatcher) Events() (e <-chan struct{}) {
|
|
||||||
return w.OnEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add implements the aghos.FSWatcher interface for *FSWatcher.
|
|
||||||
func (w *FSWatcher) Add(name string) (err error) {
|
|
||||||
return w.OnAdd(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the aghos.FSWatcher interface for *FSWatcher.
|
|
||||||
func (w *FSWatcher) Close() (err error) {
|
|
||||||
return w.OnClose()
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package aghtest
|
|
||||||
|
|
||||||
import "io/fs"
|
|
||||||
|
|
||||||
// type check
|
|
||||||
var _ fs.FS = &FS{}
|
|
||||||
|
|
||||||
// FS is a mock fs.FS implementation to use in tests.
|
|
||||||
type FS struct {
|
|
||||||
OnOpen func(name string) (fs.File, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements the fs.FS interface for *FS.
|
|
||||||
func (fsys *FS) Open(name string) (fs.File, error) {
|
|
||||||
return fsys.OnOpen(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// type check
|
|
||||||
var _ fs.StatFS = &StatFS{}
|
|
||||||
|
|
||||||
// StatFS is a mock fs.StatFS implementation to use in tests.
|
|
||||||
type StatFS struct {
|
|
||||||
// FS is embedded here to avoid implementing all it's methods.
|
|
||||||
FS
|
|
||||||
OnStat func(name string) (fs.FileInfo, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat implements the fs.StatFS interface for *StatFS.
|
|
||||||
func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) {
|
|
||||||
return fsys.OnStat(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// type check
|
|
||||||
var _ fs.GlobFS = &GlobFS{}
|
|
||||||
|
|
||||||
// GlobFS is a mock fs.GlobFS implementation to use in tests.
|
|
||||||
type GlobFS struct {
|
|
||||||
// FS is embedded here to avoid implementing all it's methods.
|
|
||||||
FS
|
|
||||||
OnGlob func(pattern string) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Glob implements the fs.GlobFS interface for *GlobFS.
|
|
||||||
func (fsys *GlobFS) Glob(pattern string) ([]string, error) {
|
|
||||||
return fsys.OnGlob(pattern)
|
|
||||||
}
|
|
||||||
68
internal/aghtime/duration.go
Normal file
68
internal/aghtime/duration.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Package aghtime defines some types for convenient work with time values.
|
||||||
|
package aghtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration is a wrapper for time.Duration providing functionality for encoding.
|
||||||
|
type Duration struct {
|
||||||
|
// time.Duration is embedded here to avoid implementing all the methods.
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer interface for Duration. It wraps
|
||||||
|
// time.Duration.String method and additionally cuts off non-leading zero values
|
||||||
|
// of minutes and seconds. Some values which are differ between the
|
||||||
|
// implementations:
|
||||||
|
//
|
||||||
|
// Duration: "1m", time.Duration: "1m0s"
|
||||||
|
// Duration: "1h", time.Duration: "1h0m0s"
|
||||||
|
// Duration: "1h1m", time.Duration: "1h1m0s"
|
||||||
|
//
|
||||||
|
func (d Duration) String() (str string) {
|
||||||
|
str = d.Duration.String()
|
||||||
|
|
||||||
|
const (
|
||||||
|
tailMin = len(`0s`)
|
||||||
|
tailMinSec = len(`0m0s`)
|
||||||
|
|
||||||
|
secsInHour = time.Hour / time.Second
|
||||||
|
minsInHour = time.Hour / time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
switch rounded := d.Duration / time.Second; {
|
||||||
|
case
|
||||||
|
rounded == 0,
|
||||||
|
rounded*time.Second != d.Duration,
|
||||||
|
rounded%60 != 0:
|
||||||
|
// Return the uncut value if it's either equal to zero or has
|
||||||
|
// fractions of a second or even whole seconds in it.
|
||||||
|
return str
|
||||||
|
|
||||||
|
case (rounded%secsInHour)/minsInHour != 0:
|
||||||
|
return str[:len(str)-tailMin]
|
||||||
|
|
||||||
|
default:
|
||||||
|
return str[:len(str)-tailMinSec]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface for Duration.
|
||||||
|
func (d Duration) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(d.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface for
|
||||||
|
// *Duration.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Make it able to parse larger units like days.
|
||||||
|
func (d *Duration) UnmarshalText(b []byte) (err error) {
|
||||||
|
defer func() { err = errors.Annotate(err, "unmarshaling duration: %w") }()
|
||||||
|
|
||||||
|
d.Duration, err = time.ParseDuration(string(b))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
240
internal/aghtime/duration_test.go
Normal file
240
internal/aghtime/duration_test.go
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
package aghtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDuration_String(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
val time.Duration
|
||||||
|
}{{
|
||||||
|
name: "1s",
|
||||||
|
val: time.Second,
|
||||||
|
}, {
|
||||||
|
name: "1m",
|
||||||
|
val: time.Minute,
|
||||||
|
}, {
|
||||||
|
name: "1h",
|
||||||
|
val: time.Hour,
|
||||||
|
}, {
|
||||||
|
name: "1m1s",
|
||||||
|
val: time.Minute + time.Second,
|
||||||
|
}, {
|
||||||
|
name: "1h1m",
|
||||||
|
val: time.Hour + time.Minute,
|
||||||
|
}, {
|
||||||
|
name: "1h0m1s",
|
||||||
|
val: time.Hour + time.Second,
|
||||||
|
}, {
|
||||||
|
name: "1ms",
|
||||||
|
val: time.Millisecond,
|
||||||
|
}, {
|
||||||
|
name: "1h0m0.001s",
|
||||||
|
val: time.Hour + time.Millisecond,
|
||||||
|
}, {
|
||||||
|
name: "1.001s",
|
||||||
|
val: time.Second + time.Millisecond,
|
||||||
|
}, {
|
||||||
|
name: "1m1.001s",
|
||||||
|
val: time.Minute + time.Second + time.Millisecond,
|
||||||
|
}, {
|
||||||
|
name: "0s",
|
||||||
|
val: 0,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
d := Duration{Duration: tc.val}
|
||||||
|
assert.Equal(t, tc.name, d.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationEncodingTester is a helper struct to simplify testing different
|
||||||
|
// Duration marshalling and unmarshaling cases.
|
||||||
|
type durationEncodingTester struct {
|
||||||
|
PtrMap map[string]*Duration `json:"ptr_map" yaml:"ptr_map"`
|
||||||
|
PtrSlice []*Duration `json:"ptr_slice" yaml:"ptr_slice"`
|
||||||
|
PtrValue *Duration `json:"ptr_value" yaml:"ptr_value"`
|
||||||
|
PtrArray [1]*Duration `json:"ptr_array" yaml:"ptr_array"`
|
||||||
|
Map map[string]Duration `json:"map" yaml:"map"`
|
||||||
|
Slice []Duration `json:"slice" yaml:"slice"`
|
||||||
|
Value Duration `json:"value" yaml:"value"`
|
||||||
|
Array [1]Duration `json:"array" yaml:"array"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const nl = "\n"
|
||||||
|
const (
|
||||||
|
jsonStr = `{` +
|
||||||
|
`"ptr_map":{"dur":"1ms"},` +
|
||||||
|
`"ptr_slice":["1ms"],` +
|
||||||
|
`"ptr_value":"1ms",` +
|
||||||
|
`"ptr_array":["1ms"],` +
|
||||||
|
`"map":{"dur":"1ms"},` +
|
||||||
|
`"slice":["1ms"],` +
|
||||||
|
`"value":"1ms",` +
|
||||||
|
`"array":["1ms"]` +
|
||||||
|
`}`
|
||||||
|
yamlStr = `ptr_map:` + nl +
|
||||||
|
` dur: 1ms` + nl +
|
||||||
|
`ptr_slice:` + nl +
|
||||||
|
`- 1ms` + nl +
|
||||||
|
`ptr_value: 1ms` + nl +
|
||||||
|
`ptr_array:` + nl +
|
||||||
|
`- 1ms` + nl +
|
||||||
|
`map:` + nl +
|
||||||
|
` dur: 1ms` + nl +
|
||||||
|
`slice:` + nl +
|
||||||
|
`- 1ms` + nl +
|
||||||
|
`value: 1ms` + nl +
|
||||||
|
`array:` + nl +
|
||||||
|
`- 1ms`
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultTestDur is the default time.Duration value to be used throughout the tests of
|
||||||
|
// Duration.
|
||||||
|
const defaultTestDur = time.Millisecond
|
||||||
|
|
||||||
|
// checkFields verifies m's fields. It expects the m to be unmarshaled from
|
||||||
|
// one of the constant strings above.
|
||||||
|
func (m *durationEncodingTester) checkFields(t *testing.T, d Duration) {
|
||||||
|
t.Run("pointers_map", func(t *testing.T) {
|
||||||
|
require.NotNil(t, m.PtrMap)
|
||||||
|
|
||||||
|
fromPtrMap, ok := m.PtrMap["dur"]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotNil(t, fromPtrMap)
|
||||||
|
|
||||||
|
assert.Equal(t, d, *fromPtrMap)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointers_slice", func(t *testing.T) {
|
||||||
|
require.Len(t, m.PtrSlice, 1)
|
||||||
|
|
||||||
|
fromPtrSlice := m.PtrSlice[0]
|
||||||
|
require.NotNil(t, fromPtrSlice)
|
||||||
|
|
||||||
|
assert.Equal(t, d, *fromPtrSlice)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointers_array", func(t *testing.T) {
|
||||||
|
fromPtrArray := m.PtrArray[0]
|
||||||
|
require.NotNil(t, fromPtrArray)
|
||||||
|
|
||||||
|
assert.Equal(t, d, *fromPtrArray)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer_value", func(t *testing.T) {
|
||||||
|
require.NotNil(t, m.PtrValue)
|
||||||
|
|
||||||
|
assert.Equal(t, d, *m.PtrValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map", func(t *testing.T) {
|
||||||
|
fromMap, ok := m.Map["dur"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.Equal(t, d, fromMap)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("slice", func(t *testing.T) {
|
||||||
|
require.Len(t, m.Slice, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, d, m.Slice[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("array", func(t *testing.T) {
|
||||||
|
assert.Equal(t, d, m.Array[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("value", func(t *testing.T) {
|
||||||
|
assert.Equal(t, d, m.Value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuration_MarshalText(t *testing.T) {
|
||||||
|
d := Duration{defaultTestDur}
|
||||||
|
dPtr := &d
|
||||||
|
|
||||||
|
v := durationEncodingTester{
|
||||||
|
PtrMap: map[string]*Duration{"dur": dPtr},
|
||||||
|
PtrSlice: []*Duration{dPtr},
|
||||||
|
PtrValue: dPtr,
|
||||||
|
PtrArray: [1]*Duration{dPtr},
|
||||||
|
Map: map[string]Duration{"dur": d},
|
||||||
|
Slice: []Duration{d},
|
||||||
|
Value: d,
|
||||||
|
Array: [1]Duration{d},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
t.Run("json", func(t *testing.T) {
|
||||||
|
t.Cleanup(b.Reset)
|
||||||
|
err := json.NewEncoder(b).Encode(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, jsonStr, b.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("yaml", func(t *testing.T) {
|
||||||
|
t.Cleanup(b.Reset)
|
||||||
|
err := yaml.NewEncoder(b).Encode(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.YAMLEq(t, yamlStr, b.String(), b.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("direct", func(t *testing.T) {
|
||||||
|
data, err := d.MarshalText()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, []byte(defaultTestDur.String()), data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuration_UnmarshalText(t *testing.T) {
|
||||||
|
d := Duration{defaultTestDur}
|
||||||
|
var v *durationEncodingTester
|
||||||
|
|
||||||
|
t.Run("json", func(t *testing.T) {
|
||||||
|
v = &durationEncodingTester{}
|
||||||
|
|
||||||
|
r := strings.NewReader(jsonStr)
|
||||||
|
err := json.NewDecoder(r).Decode(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v.checkFields(t, d)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("yaml", func(t *testing.T) {
|
||||||
|
v = &durationEncodingTester{}
|
||||||
|
|
||||||
|
r := strings.NewReader(yamlStr)
|
||||||
|
err := yaml.NewDecoder(r).Decode(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v.checkFields(t, d)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("direct", func(t *testing.T) {
|
||||||
|
dd := &Duration{}
|
||||||
|
|
||||||
|
err := dd.UnmarshalText([]byte(d.String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, d, *dd)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad_data", func(t *testing.T) {
|
||||||
|
assert.Error(t, (&Duration{}).UnmarshalText([]byte(`abc`)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,17 +5,33 @@ package dhcpd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// broadcast sends resp to the broadcast address specific for network interface.
|
// broadcast sends resp to the broadcast address specific for network interface.
|
||||||
func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err error) {
|
func (s *v4Server) broadcast(peer net.Addr, conn net.PacketConn, resp *dhcpv4.DHCPv4) {
|
||||||
|
// peer is expected to be of type *net.UDPConn as the server4.NewServer
|
||||||
|
// initializes it.
|
||||||
|
udpPeer, ok := peer.(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
log.Error("dhcpv4: peer is of unexpected type %T", peer)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
|
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
|
||||||
// options to allow broadcasting, it also binds the connection to a
|
// options to allow broadcasting, it also binds the connection to a
|
||||||
// specific interface. On FreeBSD and OpenBSD net.UDPConn.WriteTo
|
// specific interface. On FreeBSD and OpenBSD conn.WriteTo causes
|
||||||
// causes errors while writing to the addresses that belong to another
|
// errors while writing to the addresses that belong to another
|
||||||
// interface. So, use the broadcast address specific for the interface
|
// interface. So, use the broadcast address specific for the binded
|
||||||
// bound.
|
// interface.
|
||||||
peer.IP = c.bcastIP
|
udpPeer.IP = s.conf.broadcastIP
|
||||||
|
|
||||||
return c.udpConn.WriteTo(respData, peer)
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
|
if _, err := conn.WriteTo(resp.ToBytes(), peer); err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"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"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDHCPConn_Broadcast(t *testing.T) {
|
func TestV4Server_Send_broadcast(t *testing.T) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
var peer *net.UDPAddr
|
var peer *net.UDPAddr
|
||||||
|
|
||||||
udpConn := &fakePacketConn{
|
conn := &fakePacketConn{
|
||||||
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
udpPeer, ok := addr.(*net.UDPAddr)
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
@@ -30,22 +31,57 @@ func TestDHCPConn_Broadcast(t *testing.T) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
conn := &dhcpConn{
|
|
||||||
udpConn: udpConn,
|
|
||||||
bcastIP: net.IP{1, 2, 3, 255},
|
|
||||||
}
|
|
||||||
defaultPeer := &net.UDPAddr{
|
defaultPeer := &net.UDPAddr{
|
||||||
IP: net.IP{1, 2, 3, 4},
|
IP: net.IP{1, 2, 3, 4},
|
||||||
// Use neither client nor server port.
|
// Use neither client nor server port.
|
||||||
Port: 1234,
|
Port: 1234,
|
||||||
}
|
}
|
||||||
respData := (&dhcpv4.DHCPv4{}).ToBytes()
|
s := &v4Server{
|
||||||
|
conf: V4ServerConf{
|
||||||
_, _ = conn.broadcast(respData, cloneUDPAddr(defaultPeer))
|
broadcastIP: net.IP{1, 2, 3, 255},
|
||||||
|
},
|
||||||
assert.EqualValues(t, respData, b.Bytes())
|
}
|
||||||
assert.Equal(t, &net.UDPAddr{
|
|
||||||
IP: conn.bcastIP,
|
testCases := []struct {
|
||||||
Port: defaultPeer.Port,
|
name string
|
||||||
}, peer)
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
}{{
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "fully_unspecified",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
ClientIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
assert.EqualValues(t, tc.resp.ToBytes(), b.Bytes())
|
||||||
|
assert.Equal(t, &net.UDPAddr{
|
||||||
|
IP: s.conf.broadcastIP,
|
||||||
|
Port: defaultPeer.Port,
|
||||||
|
Zone: defaultPeer.Zone,
|
||||||
|
}, peer)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
peer = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,17 @@ package dhcpd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// broadcast sends resp to the broadcast address specific for network interface.
|
// broadcast sends resp to the broadcast address specific for network interface.
|
||||||
func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err error) {
|
func (s *v4Server) broadcast(peer net.Addr, conn net.PacketConn, resp *dhcpv4.DHCPv4) {
|
||||||
|
respData := resp.ToBytes()
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
// This write to 0xffffffff reverts some behavior changes made in
|
// This write to 0xffffffff reverts some behavior changes made in
|
||||||
// https://github.com/AdguardTeam/AdGuardHome/issues/3289. The DHCP
|
// https://github.com/AdguardTeam/AdGuardHome/issues/3289. The DHCP
|
||||||
// server should broadcast the message to 0xffffffff but it's
|
// server should broadcast the message to 0xffffffff but it's
|
||||||
@@ -19,13 +26,26 @@ func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err err
|
|||||||
// https://github.com/AdguardTeam/AdGuardHome/issues/3366.
|
// https://github.com/AdguardTeam/AdGuardHome/issues/3366.
|
||||||
//
|
//
|
||||||
// See also https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
// See also https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
||||||
if n, err = c.udpConn.WriteTo(respData, peer); err != nil {
|
if _, err := conn.WriteTo(respData, peer); err != nil {
|
||||||
return n, err
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer is expected to be of type *net.UDPConn as the server4.NewServer
|
||||||
|
// initializes it.
|
||||||
|
udpPeer, ok := peer.(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
log.Error("dhcpv4: peer is of unexpected type %T", peer)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast the message one more time using the interface-specific
|
// Broadcast the message one more time using the interface-specific
|
||||||
// broadcast address.
|
// broadcast address.
|
||||||
peer.IP = c.bcastIP
|
udpPeer.IP = s.conf.broadcastIP
|
||||||
|
|
||||||
return c.udpConn.WriteTo(respData, peer)
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
|
|
||||||
|
if _, err := conn.WriteTo(respData, peer); err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"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"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDHCPConn_Broadcast(t *testing.T) {
|
func TestV4Server_Send_broadcast(t *testing.T) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
var peers []*net.UDPAddr
|
var peers []*net.UDPAddr
|
||||||
|
|
||||||
udpConn := &fakePacketConn{
|
conn := &fakePacketConn{
|
||||||
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
udpPeer, ok := addr.(*net.UDPAddr)
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
@@ -30,27 +31,66 @@ func TestDHCPConn_Broadcast(t *testing.T) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
conn := &dhcpConn{
|
|
||||||
udpConn: udpConn,
|
|
||||||
bcastIP: net.IP{1, 2, 3, 255},
|
|
||||||
}
|
|
||||||
defaultPeer := &net.UDPAddr{
|
defaultPeer := &net.UDPAddr{
|
||||||
IP: net.IP{1, 2, 3, 4},
|
IP: net.IP{1, 2, 3, 4},
|
||||||
// Use neither client nor server port.
|
// Use neither client nor server port.
|
||||||
Port: 1234,
|
Port: 1234,
|
||||||
}
|
}
|
||||||
respData := (&dhcpv4.DHCPv4{}).ToBytes()
|
s := &v4Server{
|
||||||
|
conf: V4ServerConf{
|
||||||
|
broadcastIP: net.IP{1, 2, 3, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
_, _ = conn.broadcast(respData, cloneUDPAddr(defaultPeer))
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
}{{
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "fully_unspecified",
|
||||||
|
req: &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
|
ClientIPAddr: netutil.IPv4Zero(),
|
||||||
|
},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
// The same response is written twice but for different peers.
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
|
||||||
|
// The same response is written twice.
|
||||||
|
respData := tc.resp.ToBytes()
|
||||||
assert.EqualValues(t, append(respData, respData...), b.Bytes())
|
assert.EqualValues(t, append(respData, respData...), b.Bytes())
|
||||||
|
|
||||||
require.Len(t, peers, 2)
|
require.Len(t, peers, 2)
|
||||||
|
|
||||||
assert.Equal(t, cloneUDPAddr(defaultPeer), peers[0])
|
|
||||||
assert.Equal(t, &net.UDPAddr{
|
assert.Equal(t, &net.UDPAddr{
|
||||||
IP: conn.bcastIP,
|
IP: defaultPeer.IP,
|
||||||
|
Port: defaultPeer.Port,
|
||||||
|
}, peers[0])
|
||||||
|
assert.Equal(t, &net.UDPAddr{
|
||||||
|
IP: s.conf.broadcastIP,
|
||||||
Port: defaultPeer.Port,
|
Port: defaultPeer.Port,
|
||||||
}, peers[1])
|
}, peers[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
peers = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,244 +0,0 @@
|
|||||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
|
||||||
"github.com/mdlayher/ethernet"
|
|
||||||
"github.com/mdlayher/raw"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dhcpUnicastAddr is the combination of MAC and IP addresses for responding to
|
|
||||||
// the unconfigured host.
|
|
||||||
type dhcpUnicastAddr struct {
|
|
||||||
// raw.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
|
|
||||||
// actually implementing all methods. It also contains the client's
|
|
||||||
// hardware address.
|
|
||||||
raw.Addr
|
|
||||||
|
|
||||||
// yiaddr is an IP address just allocated by server for the host.
|
|
||||||
yiaddr net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
// dhcpConn is the net.PacketConn capable of handling both net.UDPAddr and
|
|
||||||
// net.HardwareAddr.
|
|
||||||
type dhcpConn struct {
|
|
||||||
// udpConn is the connection for UDP addresses.
|
|
||||||
udpConn net.PacketConn
|
|
||||||
// bcastIP is the broadcast address specific for the configured
|
|
||||||
// interface's subnet.
|
|
||||||
bcastIP net.IP
|
|
||||||
|
|
||||||
// rawConn is the connection for MAC addresses.
|
|
||||||
rawConn net.PacketConn
|
|
||||||
// srcMAC is the hardware address of the configured network interface.
|
|
||||||
srcMAC net.HardwareAddr
|
|
||||||
// srcIP is the IP address of the configured network interface.
|
|
||||||
srcIP net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDHCPConn creates the special connection for DHCP server.
|
|
||||||
func (s *v4Server) newDHCPConn(ifi *net.Interface) (c net.PacketConn, err error) {
|
|
||||||
// Create the raw connection.
|
|
||||||
var ucast net.PacketConn
|
|
||||||
if ucast, err = raw.ListenPacket(ifi, uint16(ethernet.EtherTypeIPv4), nil); err != nil {
|
|
||||||
return nil, fmt.Errorf("creating raw udp connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the UDP connection.
|
|
||||||
var bcast net.PacketConn
|
|
||||||
bcast, err = server4.NewIPv4UDPConn(ifi.Name, &net.UDPAddr{
|
|
||||||
// TODO(e.burkov): Listening on zeroes makes the server handle
|
|
||||||
// requests from all the interfaces. Inspect the ways to
|
|
||||||
// specify the interface-specific listening addresses.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
|
||||||
IP: net.IP{0, 0, 0, 0},
|
|
||||||
Port: dhcpv4.ServerPort,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating ipv4 udp connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dhcpConn{
|
|
||||||
udpConn: bcast,
|
|
||||||
bcastIP: s.conf.broadcastIP,
|
|
||||||
rawConn: ucast,
|
|
||||||
srcMAC: ifi.HardwareAddr,
|
|
||||||
srcIP: s.conf.dnsIPAddrs[0],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapErrs is a helper to wrap the errors from two independent underlying
|
|
||||||
// connections.
|
|
||||||
func (c *dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
|
||||||
switch {
|
|
||||||
case udpConnErr != nil && rawConnErr != nil:
|
|
||||||
return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr)
|
|
||||||
case udpConnErr != nil:
|
|
||||||
return fmt.Errorf("%s udp connection: %w", action, udpConnErr)
|
|
||||||
case rawConnErr != nil:
|
|
||||||
return fmt.Errorf("%s raw connection: %w", action, rawConnErr)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying
|
|
||||||
// connection to write to based on the type of addr.
|
|
||||||
func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
switch addr := addr.(type) {
|
|
||||||
case *dhcpUnicastAddr:
|
|
||||||
// Unicast the message to the client's MAC address. Use the raw
|
|
||||||
// connection.
|
|
||||||
//
|
|
||||||
// Note: unicasting is performed on the only network interface
|
|
||||||
// that is configured. For now it may be not what users expect
|
|
||||||
// so additionally broadcast the message via UDP connection.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
|
||||||
var rerr error
|
|
||||||
n, rerr = c.unicast(p, addr)
|
|
||||||
|
|
||||||
_, uerr := c.broadcast(p, &net.UDPAddr{
|
|
||||||
IP: netutil.IPv4bcast(),
|
|
||||||
Port: dhcpv4.ClientPort,
|
|
||||||
})
|
|
||||||
|
|
||||||
return n, c.wrapErrs("writing to", uerr, rerr)
|
|
||||||
case *net.UDPAddr:
|
|
||||||
if addr.IP.Equal(net.IPv4bcast) {
|
|
||||||
// Broadcast the message for the client which supports
|
|
||||||
// it. Use the UDP connection.
|
|
||||||
return c.broadcast(p, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unicast the message to the client's IP address. Use the UDP
|
|
||||||
// connection.
|
|
||||||
return c.udpConn.WriteTo(p, addr)
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("peer is of unexpected type %T", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
return c.udpConn.ReadFrom(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unicast wraps respData with required frames and writes it to the peer.
|
|
||||||
func (c *dhcpConn) unicast(respData []byte, peer *dhcpUnicastAddr) (n int, err error) {
|
|
||||||
var data []byte
|
|
||||||
data, err = c.buildEtherPkt(respData, peer)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.rawConn.WriteTo(data, &peer.Addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) Close() (err error) {
|
|
||||||
rerr := c.rawConn.Close()
|
|
||||||
if errors.Is(rerr, os.ErrClosed) {
|
|
||||||
// Ignore the error since the actual file is closed already.
|
|
||||||
rerr = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.wrapErrs("closing", c.udpConn.Close(), rerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) LocalAddr() (a net.Addr) {
|
|
||||||
return c.udpConn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) SetDeadline(t time.Time) (err error) {
|
|
||||||
return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return c.wrapErrs(
|
|
||||||
"setting reading deadline on",
|
|
||||||
c.udpConn.SetReadDeadline(t),
|
|
||||||
c.rawConn.SetReadDeadline(t),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline implements net.PacketConn for *dhcpConn.
|
|
||||||
func (c *dhcpConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return c.wrapErrs(
|
|
||||||
"setting writing deadline on",
|
|
||||||
c.udpConn.SetWriteDeadline(t),
|
|
||||||
c.rawConn.SetWriteDeadline(t),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipv4DefaultTTL is the default Time to Live value as recommended by
|
|
||||||
// RFC-1700 (https://datatracker.ietf.org/doc/html/rfc1700) in seconds.
|
|
||||||
const ipv4DefaultTTL = 64
|
|
||||||
|
|
||||||
// errInvalidPktDHCP is returned when the provided payload is not a valid DHCP
|
|
||||||
// packet.
|
|
||||||
const errInvalidPktDHCP errors.Error = "packet is not a valid dhcp packet"
|
|
||||||
|
|
||||||
// buildEtherPkt wraps the payload with IPv4, UDP and Ethernet frames. The
|
|
||||||
// payload is expected to be an encoded DHCP packet.
|
|
||||||
func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []byte, err error) {
|
|
||||||
dhcpLayer := gopacket.NewPacket(payload, layers.LayerTypeDHCPv4, gopacket.DecodeOptions{
|
|
||||||
NoCopy: true,
|
|
||||||
}).Layer(layers.LayerTypeDHCPv4)
|
|
||||||
|
|
||||||
// Check if the decoding succeeded and the resulting layer doesn't
|
|
||||||
// contain any errors. It should guarantee panic-safe converting of the
|
|
||||||
// layer into gopacket.SerializableLayer.
|
|
||||||
if dhcpLayer == nil || dhcpLayer.LayerType() != layers.LayerTypeDHCPv4 {
|
|
||||||
return nil, errInvalidPktDHCP
|
|
||||||
}
|
|
||||||
|
|
||||||
udpLayer := &layers.UDP{
|
|
||||||
SrcPort: dhcpv4.ServerPort,
|
|
||||||
DstPort: dhcpv4.ClientPort,
|
|
||||||
}
|
|
||||||
ipv4Layer := &layers.IPv4{
|
|
||||||
Version: uint8(layers.IPProtocolIPv4),
|
|
||||||
Flags: layers.IPv4DontFragment,
|
|
||||||
TTL: ipv4DefaultTTL,
|
|
||||||
Protocol: layers.IPProtocolUDP,
|
|
||||||
SrcIP: c.srcIP,
|
|
||||||
DstIP: peer.yiaddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore the error since it's only returned for invalid network layer's
|
|
||||||
// type.
|
|
||||||
_ = udpLayer.SetNetworkLayerForChecksum(ipv4Layer)
|
|
||||||
ethLayer := &layers.Ethernet{
|
|
||||||
SrcMAC: c.srcMAC,
|
|
||||||
DstMAC: peer.HardwareAddr,
|
|
||||||
EthernetType: layers.EthernetTypeIPv4,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := gopacket.NewSerializeBuffer()
|
|
||||||
err = gopacket.SerializeLayers(buf, gopacket.SerializeOptions{
|
|
||||||
FixLengths: true,
|
|
||||||
ComputeChecksums: true,
|
|
||||||
}, ethLayer, ipv4Layer, udpLayer, dhcpLayer.(gopacket.SerializableLayer))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("serializing layers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
||||||
"github.com/mdlayher/raw"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDHCPConn_WriteTo_common(t *testing.T) {
|
|
||||||
respData := (&dhcpv4.DHCPv4{}).ToBytes()
|
|
||||||
udpAddr := &net.UDPAddr{
|
|
||||||
IP: net.IP{1, 2, 3, 4},
|
|
||||||
Port: dhcpv4.ClientPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("unicast_ip", func(t *testing.T) {
|
|
||||||
writeTo := func(_ []byte, addr net.Addr) (_ int, _ error) {
|
|
||||||
assert.Equal(t, udpAddr, addr)
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &dhcpConn{udpConn: &fakePacketConn{writeTo: writeTo}}
|
|
||||||
|
|
||||||
_, err := conn.WriteTo(respData, udpAddr)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unexpected_addr_type", func(t *testing.T) {
|
|
||||||
type unexpectedAddrType struct {
|
|
||||||
net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &dhcpConn{}
|
|
||||||
n, err := conn.WriteTo(nil, &unexpectedAddrType{})
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
testutil.AssertErrorMsg(t, "peer is of unexpected type *dhcpd.unexpectedAddrType", err)
|
|
||||||
assert.Zero(t, n)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildEtherPkt(t *testing.T) {
|
|
||||||
conn := &dhcpConn{
|
|
||||||
srcMAC: net.HardwareAddr{1, 2, 3, 4, 5, 6},
|
|
||||||
srcIP: net.IP{1, 2, 3, 4},
|
|
||||||
}
|
|
||||||
peer := &dhcpUnicastAddr{
|
|
||||||
Addr: raw.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
|
|
||||||
yiaddr: net.IP{4, 3, 2, 1},
|
|
||||||
}
|
|
||||||
payload := (&dhcpv4.DHCPv4{}).ToBytes()
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
pkt, err := conn.buildEtherPkt(payload, peer)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.NotEmpty(t, pkt)
|
|
||||||
|
|
||||||
actualPkt := gopacket.NewPacket(pkt, layers.LayerTypeEthernet, gopacket.DecodeOptions{
|
|
||||||
NoCopy: true,
|
|
||||||
})
|
|
||||||
require.NotNil(t, actualPkt)
|
|
||||||
|
|
||||||
wantTypes := []gopacket.LayerType{
|
|
||||||
layers.LayerTypeEthernet,
|
|
||||||
layers.LayerTypeIPv4,
|
|
||||||
layers.LayerTypeUDP,
|
|
||||||
layers.LayerTypeDHCPv4,
|
|
||||||
}
|
|
||||||
actualLayers := actualPkt.Layers()
|
|
||||||
require.Len(t, actualLayers, len(wantTypes))
|
|
||||||
|
|
||||||
for i, wantType := range wantTypes {
|
|
||||||
layer := actualLayers[i]
|
|
||||||
require.NotNil(t, layer)
|
|
||||||
|
|
||||||
assert.Equal(t, wantType, layer.LayerType())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non-serializable", func(t *testing.T) {
|
|
||||||
// Create an invalid DHCP packet.
|
|
||||||
invalidPayload := []byte{1, 2, 3, 4}
|
|
||||||
pkt, err := conn.buildEtherPkt(invalidPayload, nil)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
assert.ErrorIs(t, err, errInvalidPktDHCP)
|
|
||||||
assert.Empty(t, pkt)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("serializing_error", func(t *testing.T) {
|
|
||||||
// Create a peer with invalid MAC.
|
|
||||||
badPeer := &dhcpUnicastAddr{
|
|
||||||
Addr: raw.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
|
|
||||||
yiaddr: net.IP{4, 3, 2, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt, err := conn.buildEtherPkt(payload, badPeer)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
assert.Empty(t, pkt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -68,7 +67,9 @@ func TestDB(t *testing.T) {
|
|||||||
err = s.dbStore()
|
err = s.dbStore()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testutil.CleanupAndRequireSuccess(t, func() (err error) { return os.Remove(dbFilename) })
|
t.Cleanup(func() {
|
||||||
|
assert.NoError(t, os.Remove(dbFilename))
|
||||||
|
})
|
||||||
|
|
||||||
err = s.srv4.ResetLeases(nil)
|
err = s.srv4.ResetLeases(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -137,51 +138,8 @@ func TestNormalizeLeases(t *testing.T) {
|
|||||||
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4Server_badRange(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
gatewayIP net.IP
|
|
||||||
subnetMask net.IP
|
|
||||||
wantErrMsg string
|
|
||||||
}{{
|
|
||||||
name: "gateway_in_range",
|
|
||||||
gatewayIP: net.IP{192, 168, 10, 120},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 0},
|
|
||||||
wantErrMsg: "dhcpv4: gateway ip 192.168.10.120 in the ip range: " +
|
|
||||||
"192.168.10.20-192.168.10.200",
|
|
||||||
}, {
|
|
||||||
name: "outside_range_start",
|
|
||||||
gatewayIP: net.IP{192, 168, 10, 1},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 240},
|
|
||||||
wantErrMsg: "dhcpv4: range start 192.168.10.20 is outside network " +
|
|
||||||
"192.168.10.1/28",
|
|
||||||
}, {
|
|
||||||
name: "outside_range_end",
|
|
||||||
gatewayIP: net.IP{192, 168, 10, 1},
|
|
||||||
subnetMask: net.IP{255, 255, 255, 224},
|
|
||||||
wantErrMsg: "dhcpv4: range end 192.168.10.200 is outside network " +
|
|
||||||
"192.168.10.1/27",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
conf := V4ServerConf{
|
|
||||||
Enabled: true,
|
|
||||||
RangeStart: net.IP{192, 168, 10, 20},
|
|
||||||
RangeEnd: net.IP{192, 168, 10, 200},
|
|
||||||
GatewayIP: tc.gatewayIP,
|
|
||||||
SubnetMask: tc.subnetMask,
|
|
||||||
notify: testNotify,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := v4Create(conf)
|
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cloneUDPAddr returns a deep copy of a.
|
// cloneUDPAddr returns a deep copy of a.
|
||||||
func cloneUDPAddr(a *net.UDPAddr) (clone *net.UDPAddr) {
|
func cloneUDPAddr(a *net.UDPAddr) (copy *net.UDPAddr) {
|
||||||
return &net.UDPAddr{
|
return &net.UDPAddr{
|
||||||
IP: netutil.CloneIP(a.IP),
|
IP: netutil.CloneIP(a.IP),
|
||||||
Port: a.Port,
|
Port: a.Port,
|
||||||
|
|||||||
@@ -15,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)
|
||||||
require.NoError(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)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -65,8 +64,14 @@ func TestNewIPRange(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) {
|
||||||
_, err := newIPRange(tc.start, tc.end)
|
r, err := newIPRange(tc.start, tc.end)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, r)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -46,7 +45,13 @@ func TestNullBool_UnmarshalJSON(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var got nullBool
|
var got nullBool
|
||||||
err := got.UnmarshalJSON(tc.data)
|
err := got.UnmarshalJSON(tc.data)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ func prepareOptions(conf V4ServerConf) (opts dhcpv4.Options) {
|
|||||||
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
||||||
// The all-routers address is preferred wherever possible, see
|
// The all-routers address is preferred wherever possible, see
|
||||||
// https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
// https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||||
dhcpv4.OptionRouterSolicitationAddress.Code(): netutil.IPv4allrouter(),
|
dhcpv4.OptionRouterSolicitationAddress.Code(): net.IPv4allrouter.To4(),
|
||||||
dhcpv4.OptionBroadcastAddress.Code(): netutil.IPv4bcast(),
|
dhcpv4.OptionBroadcastAddress.Code(): net.IPv4bcast.To4(),
|
||||||
|
|
||||||
// Link-Layer Per Interface
|
// Link-Layer Per Interface
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ func TestParseOpt(t *testing.T) {
|
|||||||
opt, err := parseDHCPOption(tc.in)
|
opt, err := parseDHCPOption(tc.in)
|
||||||
if tc.wantErrMsg != "" {
|
if tc.wantErrMsg != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
assert.Equal(t, tc.wantErrMsg, err.Error())
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,11 +16,9 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
|
||||||
"github.com/go-ping/ping"
|
"github.com/go-ping/ping"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
"github.com/mdlayher/raw"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// v4Server is a DHCPv4 server.
|
// v4Server is a DHCPv4 server.
|
||||||
@@ -293,8 +291,6 @@ func (s *v4Server) addLease(l *Lease) (err error) {
|
|||||||
offset, inOffset := r.offset(l.IP)
|
offset, inOffset := r.offset(l.IP)
|
||||||
|
|
||||||
if l.IsStatic() {
|
if l.IsStatic() {
|
||||||
// TODO(a.garipov, d.seregin): Subnet can be nil when dhcp server is
|
|
||||||
// disabled.
|
|
||||||
if sn := s.conf.subnet; !sn.Contains(l.IP) {
|
if sn := s.conf.subnet; !sn.Contains(l.IP) {
|
||||||
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
||||||
}
|
}
|
||||||
@@ -902,10 +898,9 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
|||||||
resp.UpdateOption(dhcpv4.OptGeneric(code, configured.Get(code)))
|
resp.UpdateOption(dhcpv4.OptGeneric(code, configured.Get(code)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update the value of Domain Name Server option separately from others if
|
// Update the value of Domain Name Server option separately from others
|
||||||
// not assigned yet since its value is set after server's creating.
|
// since its value is set after server's creating.
|
||||||
if requested.Has(dhcpv4.OptionDomainNameServer) &&
|
if requested.Has(dhcpv4.OptionDomainNameServer) {
|
||||||
!resp.Options.Has(dhcpv4.OptionDomainNameServer) {
|
|
||||||
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,44 +955,43 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
|||||||
//
|
//
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
||||||
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||||
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
var unicast bool
|
||||||
case giaddr != nil && !giaddr.IsUnspecified():
|
if giaddr := req.GatewayIPAddr; giaddr != nil && !giaddr.IsUnspecified() {
|
||||||
// Send any return messages to the server port on the BOOTP
|
// Send any return messages to the server port on the BOOTP
|
||||||
// relay agent whose address appears in giaddr.
|
// relay agent whose address appears in giaddr.
|
||||||
peer = &net.UDPAddr{
|
peer = &net.UDPAddr{
|
||||||
IP: giaddr,
|
IP: giaddr,
|
||||||
Port: dhcpv4.ServerPort,
|
Port: dhcpv4.ServerPort,
|
||||||
}
|
}
|
||||||
if mtype == dhcpv4.MessageTypeNak {
|
unicast = true
|
||||||
// Set the broadcast bit in the DHCPNAK, so that the
|
} else if mtype := resp.MessageType(); mtype == dhcpv4.MessageTypeNak {
|
||||||
// relay agent broadcasted it to the client, because the
|
|
||||||
// client may not have a correct network address or
|
|
||||||
// subnet mask, and the client may not be answering ARP
|
|
||||||
// requests.
|
|
||||||
resp.SetBroadcast()
|
|
||||||
}
|
|
||||||
case mtype == dhcpv4.MessageTypeNak:
|
|
||||||
// Broadcast any DHCPNAK messages to 0xffffffff.
|
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||||
case ciaddr != nil && !ciaddr.IsUnspecified():
|
} else if ciaddr := req.ClientIPAddr; ciaddr != nil && !ciaddr.IsUnspecified() {
|
||||||
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||||
// ciaddr.
|
// ciaddr.
|
||||||
peer = &net.UDPAddr{
|
peer = &net.UDPAddr{
|
||||||
IP: ciaddr,
|
IP: ciaddr,
|
||||||
Port: dhcpv4.ClientPort,
|
Port: dhcpv4.ClientPort,
|
||||||
}
|
}
|
||||||
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
unicast = true
|
||||||
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
|
||||||
// hardware address and yiaddr.
|
|
||||||
peer = &dhcpUnicastAddr{
|
|
||||||
Addr: raw.Addr{HardwareAddr: req.ClientHWAddr},
|
|
||||||
yiaddr: resp.YourIPAddr,
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
// Go on since peer is already set to broadcast.
|
// TODO(e.burkov): Unicast the message to the actual link-layer address
|
||||||
|
// of the client if broadcast bit is not set. Perhaps, use custom
|
||||||
|
// connection when creating the server.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3443.
|
||||||
|
|
||||||
|
if !unicast {
|
||||||
|
s.broadcast(peer, conn, resp)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||||
if _, err := conn.WriteTo(resp.ToBytes(), peer); err != nil {
|
|
||||||
|
_, err := conn.WriteTo(resp.ToBytes(), peer)
|
||||||
|
if err != nil {
|
||||||
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1035,18 +1029,11 @@ func (s *v4Server) Start() (err error) {
|
|||||||
|
|
||||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||||
|
|
||||||
var c net.PacketConn
|
laddr := &net.UDPAddr{
|
||||||
if c, err = s.newDHCPConn(iface); err != nil {
|
IP: net.IP{0, 0, 0, 0},
|
||||||
return err
|
Port: dhcpv4.ServerPort,
|
||||||
}
|
}
|
||||||
|
s.srv, err = server4.NewServer(iface.Name, laddr, s.packetHandler, server4.WithDebugLogger())
|
||||||
s.srv, err = server4.NewServer(
|
|
||||||
iface.Name,
|
|
||||||
nil,
|
|
||||||
s.packetHandler,
|
|
||||||
server4.WithConn(c),
|
|
||||||
server4.WithDebugLogger(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1127,33 +1114,10 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
|||||||
return s, fmt.Errorf("dhcpv4: %w", err)
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.ipRange.contains(routerIP) {
|
|
||||||
return s, fmt.Errorf("dhcpv4: gateway ip %v in the ip range: %v-%v",
|
|
||||||
routerIP,
|
|
||||||
conf.RangeStart,
|
|
||||||
conf.RangeEnd,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.conf.subnet.Contains(conf.RangeStart) {
|
|
||||||
return s, fmt.Errorf("dhcpv4: range start %v is outside network %v",
|
|
||||||
conf.RangeStart,
|
|
||||||
s.conf.subnet,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.conf.subnet.Contains(conf.RangeEnd) {
|
|
||||||
return s, fmt.Errorf("dhcpv4: range end %v is outside network %v",
|
|
||||||
conf.RangeEnd,
|
|
||||||
s.conf.subnet,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(a.garipov, d.seregin): Check that every lease is inside the IPRange.
|
|
||||||
s.leasedOffsets = newBitSet()
|
s.leasedOffsets = newBitSet()
|
||||||
|
|
||||||
if conf.LeaseDuration == 0 {
|
if conf.LeaseDuration == 0 {
|
||||||
s.conf.leaseTime = timeutil.Day
|
s.conf.leaseTime = time.Hour * 24
|
||||||
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
||||||
} else {
|
} else {
|
||||||
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
||||||
|
|||||||
@@ -4,14 +4,12 @@
|
|||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/mdlayher/raw"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -19,34 +17,17 @@ import (
|
|||||||
func notify4(flags uint32) {
|
func notify4(flags uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultV4ServerConf returns the default configuration for *v4Server to use in
|
func TestV4_AddRemove_static(t *testing.T) {
|
||||||
// tests.
|
s, err := v4Create(V4ServerConf{
|
||||||
func defaultV4ServerConf() (conf V4ServerConf) {
|
|
||||||
return 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,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// defaultSrv prepares the default DHCPServer to use in tests. The underlying
|
|
||||||
// type of s is *v4Server.
|
|
||||||
func defaultSrv(t *testing.T) (s DHCPServer) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s, err = v4Create(defaultV4ServerConf())
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestV4_AddRemove_static(t *testing.T) {
|
|
||||||
s := defaultSrv(t)
|
|
||||||
|
|
||||||
ls := s.GetLeases(LeasesStatic)
|
ls := s.GetLeases(LeasesStatic)
|
||||||
assert.Empty(t, ls)
|
assert.Empty(t, ls)
|
||||||
|
|
||||||
@@ -57,7 +38,7 @@ func TestV4_AddRemove_static(t *testing.T) {
|
|||||||
IP: net.IP{192, 168, 10, 150},
|
IP: net.IP{192, 168, 10, 150},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.AddStaticLease(l)
|
err = s.AddStaticLease(l)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = s.AddStaticLease(l)
|
err = s.AddStaticLease(l)
|
||||||
@@ -85,7 +66,15 @@ func TestV4_AddRemove_static(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestV4_AddReplace(t *testing.T) {
|
func TestV4_AddReplace(t *testing.T) {
|
||||||
sIface := defaultSrv(t)
|
sIface, err := v4Create(V4ServerConf{
|
||||||
|
Enabled: true,
|
||||||
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
|
notify: notify4,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
@@ -101,7 +90,7 @@ func TestV4_AddReplace(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
for i := range dynLeases {
|
for i := range dynLeases {
|
||||||
err := s.addLease(&dynLeases[i])
|
err = s.addLease(&dynLeases[i])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +105,7 @@ func TestV4_AddReplace(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
for _, l := range stLeases {
|
for _, l := range stLeases {
|
||||||
err := s.AddStaticLease(l)
|
err = s.AddStaticLease(l)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,80 +119,17 @@ func TestV4_AddReplace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4Server_Process_optionsPriority(t *testing.T) {
|
|
||||||
defaultIP := net.IP{192, 168, 1, 1}
|
|
||||||
knownIP := net.IP{1, 2, 3, 4}
|
|
||||||
|
|
||||||
// prepareSrv creates a *v4Server and sets the opt6IPs in the initial
|
|
||||||
// configuration of the server as the value for DHCP option 6.
|
|
||||||
prepareSrv := func(t *testing.T, opt6IPs []net.IP) (s *v4Server) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
conf := defaultV4ServerConf()
|
|
||||||
if len(opt6IPs) > 0 {
|
|
||||||
b := &strings.Builder{}
|
|
||||||
stringutil.WriteToBuilder(b, "6 ips ", opt6IPs[0].String())
|
|
||||||
for _, ip := range opt6IPs[1:] {
|
|
||||||
stringutil.WriteToBuilder(b, ",", ip.String())
|
|
||||||
}
|
|
||||||
conf.Options = []string{b.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
ss, err := v4Create(conf)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
s, ok = ss.(*v4Server)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = []net.IP{defaultIP}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkResp creates a discovery message with DHCP option 6 requested amd
|
|
||||||
// asserts the response to contain wantIPs in this option.
|
|
||||||
checkResp := func(t *testing.T, s *v4Server, wantIPs []net.IP) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
|
||||||
req, err := dhcpv4.NewDiscovery(mac, dhcpv4.WithRequestedOptions(
|
|
||||||
dhcpv4.OptionDomainNameServer,
|
|
||||||
))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var resp *dhcpv4.DHCPv4
|
|
||||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res := s.process(req, resp)
|
|
||||||
require.Equal(t, 1, res)
|
|
||||||
|
|
||||||
o := resp.GetOneOption(dhcpv4.OptionDomainNameServer)
|
|
||||||
require.NotEmpty(t, o)
|
|
||||||
|
|
||||||
wantData := []byte{}
|
|
||||||
for _, ip := range wantIPs {
|
|
||||||
wantData = append(wantData, ip...)
|
|
||||||
}
|
|
||||||
assert.Equal(t, o, wantData)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
|
||||||
s := prepareSrv(t, nil)
|
|
||||||
|
|
||||||
checkResp(t, s, []net.IP{defaultIP})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("explicitly_configured", func(t *testing.T) {
|
|
||||||
s := prepareSrv(t, []net.IP{knownIP, knownIP})
|
|
||||||
|
|
||||||
checkResp(t, s, []net.IP{knownIP, knownIP})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestV4StaticLease_Get(t *testing.T) {
|
func TestV4StaticLease_Get(t *testing.T) {
|
||||||
sIface := defaultSrv(t)
|
var err error
|
||||||
|
sIface, err := v4Create(V4ServerConf{
|
||||||
|
Enabled: true,
|
||||||
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
|
notify: notify4,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
@@ -215,7 +141,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
|||||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||||
IP: net.IP{192, 168, 10, 150},
|
IP: net.IP{192, 168, 10, 150},
|
||||||
}
|
}
|
||||||
err := s.AddStaticLease(l)
|
err = s.AddStaticLease(l)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var req, resp *dhcpv4.DHCPv4
|
var req, resp *dhcpv4.DHCPv4
|
||||||
@@ -283,14 +209,19 @@ func TestV4StaticLease_Get(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestV4DynamicLease_Get(t *testing.T) {
|
func TestV4DynamicLease_Get(t *testing.T) {
|
||||||
conf := defaultV4ServerConf()
|
var err error
|
||||||
conf.Options = []string{
|
sIface, err := v4Create(V4ServerConf{
|
||||||
|
Enabled: true,
|
||||||
|
RangeStart: net.IP{192, 168, 10, 100},
|
||||||
|
RangeEnd: net.IP{192, 168, 10, 200},
|
||||||
|
GatewayIP: net.IP{192, 168, 10, 1},
|
||||||
|
SubnetMask: net.IP{255, 255, 255, 0},
|
||||||
|
notify: notify4,
|
||||||
|
Options: []string{
|
||||||
"81 hex 303132",
|
"81 hex 303132",
|
||||||
"82 ip 1.2.3.4",
|
"82 ip 1.2.3.4",
|
||||||
}
|
},
|
||||||
|
})
|
||||||
var err error
|
|
||||||
sIface, err := v4Create(conf)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s, ok := sIface.(*v4Server)
|
s, ok := sIface.(*v4Server)
|
||||||
@@ -431,7 +362,14 @@ func TestNormalizeHostname(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, err := normalizeHostname(tc.hostname)
|
got, err := normalizeHostname(tc.hostname)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -452,107 +390,67 @@ func (fc *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
return fc.writeTo(p, addr)
|
return fc.writeTo(p, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4Server_Send(t *testing.T) {
|
func TestV4Server_Send_unicast(t *testing.T) {
|
||||||
s := &v4Server{}
|
b := &bytes.Buffer{}
|
||||||
|
var peer *net.UDPAddr
|
||||||
|
|
||||||
var (
|
conn := &fakePacketConn{
|
||||||
defaultIP = net.IP{99, 99, 99, 99}
|
writeTo: func(p []byte, addr net.Addr) (n int, err error) {
|
||||||
knownIP = net.IP{4, 2, 4, 2}
|
udpPeer, ok := addr.(*net.UDPAddr)
|
||||||
knownMAC = net.HardwareAddr{6, 5, 4, 3, 2, 1}
|
require.True(t, ok)
|
||||||
)
|
|
||||||
|
peer = cloneUDPAddr(udpPeer)
|
||||||
|
|
||||||
|
n, err = b.Write(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
defaultPeer := &net.UDPAddr{
|
defaultPeer := &net.UDPAddr{
|
||||||
IP: defaultIP,
|
IP: net.IP{1, 2, 3, 4},
|
||||||
// Use neither client nor server port to check it actually
|
// Use neither client nor server port.
|
||||||
// changed.
|
Port: 1234,
|
||||||
Port: dhcpv4.ClientPort + dhcpv4.ServerPort,
|
|
||||||
}
|
}
|
||||||
defaultResp := &dhcpv4.DHCPv4{}
|
defaultResp := &dhcpv4.DHCPv4{
|
||||||
|
OpCode: dhcpv4.OpcodeBootReply,
|
||||||
|
}
|
||||||
|
s := &v4Server{}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
want net.Addr
|
|
||||||
req *dhcpv4.DHCPv4
|
|
||||||
resp *dhcpv4.DHCPv4
|
|
||||||
name string
|
name string
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
wantPeer net.Addr
|
||||||
}{{
|
}{{
|
||||||
name: "giaddr",
|
name: "relay_agent",
|
||||||
req: &dhcpv4.DHCPv4{GatewayIPAddr: knownIP},
|
req: &dhcpv4.DHCPv4{
|
||||||
resp: defaultResp,
|
GatewayIPAddr: defaultPeer.IP,
|
||||||
want: &net.UDPAddr{
|
},
|
||||||
IP: knownIP,
|
wantPeer: &net.UDPAddr{
|
||||||
|
IP: defaultPeer.IP,
|
||||||
Port: dhcpv4.ServerPort,
|
Port: dhcpv4.ServerPort,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "nak",
|
name: "known_client",
|
||||||
req: &dhcpv4.DHCPv4{},
|
req: &dhcpv4.DHCPv4{
|
||||||
resp: &dhcpv4.DHCPv4{
|
GatewayIPAddr: netutil.IPv4Zero(),
|
||||||
Options: dhcpv4.OptionsFromList(
|
ClientIPAddr: net.IP{2, 3, 4, 5},
|
||||||
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
want: defaultPeer,
|
wantPeer: &net.UDPAddr{
|
||||||
}, {
|
IP: net.IP{2, 3, 4, 5},
|
||||||
name: "ciaddr",
|
|
||||||
req: &dhcpv4.DHCPv4{ClientIPAddr: knownIP},
|
|
||||||
resp: &dhcpv4.DHCPv4{},
|
|
||||||
want: &net.UDPAddr{
|
|
||||||
IP: knownIP,
|
|
||||||
Port: dhcpv4.ClientPort,
|
Port: dhcpv4.ClientPort,
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
name: "chaddr",
|
|
||||||
req: &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
|
|
||||||
resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
|
|
||||||
want: &dhcpUnicastAddr{
|
|
||||||
Addr: raw.Addr{HardwareAddr: knownMAC},
|
|
||||||
yiaddr: knownIP,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "who_are_you",
|
|
||||||
req: &dhcpv4.DHCPv4{},
|
|
||||||
resp: &dhcpv4.DHCPv4{},
|
|
||||||
want: defaultPeer,
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
conn := &fakePacketConn{
|
s.send(defaultPeer, conn, tc.req, defaultResp)
|
||||||
writeTo: func(_ []byte, addr net.Addr) (_ int, _ error) {
|
assert.EqualValues(t, defaultResp.ToBytes(), b.Bytes())
|
||||||
assert.Equal(t, tc.want, addr)
|
assert.Equal(t, tc.wantPeer, peer)
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("giaddr_nak", func(t *testing.T) {
|
b.Reset()
|
||||||
req := &dhcpv4.DHCPv4{
|
peer = nil
|
||||||
GatewayIPAddr: knownIP,
|
|
||||||
}
|
}
|
||||||
// Ensure the request is for unicast.
|
|
||||||
req.SetUnicast()
|
|
||||||
resp := &dhcpv4.DHCPv4{
|
|
||||||
Options: dhcpv4.OptionsFromList(
|
|
||||||
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
want := &net.UDPAddr{
|
|
||||||
IP: req.GatewayIPAddr,
|
|
||||||
Port: dhcpv4.ServerPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &fakePacketConn{
|
|
||||||
writeTo: func(_ []byte, addr net.Addr) (n int, err error) {
|
|
||||||
assert.Equal(t, want, addr)
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s.send(cloneUDPAddr(defaultPeer), conn, req, resp)
|
|
||||||
assert.True(t, resp.IsBroadcast())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6/server6"
|
"github.com/insomniacslk/dhcp/dhcpv6/server6"
|
||||||
"github.com/insomniacslk/dhcp/iana"
|
"github.com/insomniacslk/dhcp/iana"
|
||||||
@@ -708,7 +707,7 @@ func v6Create(conf V6ServerConf) (DHCPServer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conf.LeaseDuration == 0 {
|
if conf.LeaseDuration == 0 {
|
||||||
s.conf.leaseTime = timeutil.Day
|
s.conf.leaseTime = time.Hour * 24
|
||||||
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
||||||
} else {
|
} else {
|
||||||
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"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.
|
||||||
@@ -179,7 +179,13 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
|||||||
clientID, err := srv.clientIDFromDNSContext(pctx)
|
clientID, err := srv.clientIDFromDNSContext(pctx)
|
||||||
assert.Equal(t, tc.wantClientID, clientID)
|
assert.Equal(t, tc.wantClientID, clientID)
|
||||||
|
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +250,13 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) {
|
|||||||
clientID, err := clientIDFromDNSContextHTTPS(pctx)
|
clientID, err := clientIDFromDNSContextHTTPS(pctx)
|
||||||
assert.Equal(t, tc.wantClientID, clientID)
|
assert.Equal(t, tc.wantClientID, clientID)
|
||||||
|
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtime"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
@@ -18,7 +19,6 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
|
||||||
"github.com/ameshkov/dnscrypt/v2"
|
"github.com/ameshkov/dnscrypt/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ type FilteringConfig struct {
|
|||||||
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
|
FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm
|
||||||
// FastestTimeout replaces the default timeout for dialing IP addresses
|
// FastestTimeout replaces the default timeout for dialing IP addresses
|
||||||
// when FastestAddr is true.
|
// when FastestAddr is true.
|
||||||
FastestTimeout timeutil.Duration `yaml:"fastest_timeout"`
|
FastestTimeout aghtime.Duration `yaml:"fastest_timeout"`
|
||||||
|
|
||||||
// Access settings
|
// Access settings
|
||||||
// --
|
// --
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import (
|
|||||||
|
|
||||||
// To transfer information between modules
|
// To transfer information between modules
|
||||||
type dnsContext struct {
|
type dnsContext struct {
|
||||||
|
// TODO(a.garipov): Remove this and rewrite processors to be methods of
|
||||||
|
// *Server instead.
|
||||||
|
srv *Server
|
||||||
proxyCtx *proxy.DNSContext
|
proxyCtx *proxy.DNSContext
|
||||||
// setts are the filtering settings for the client.
|
// setts are the filtering settings for the client.
|
||||||
setts *filtering.Settings
|
setts *filtering.Settings
|
||||||
@@ -25,8 +28,7 @@ type dnsContext struct {
|
|||||||
// response is modified by filters.
|
// response is modified by filters.
|
||||||
origResp *dns.Msg
|
origResp *dns.Msg
|
||||||
// unreversedReqIP stores an IP address obtained from PTR request if it
|
// unreversedReqIP stores an IP address obtained from PTR request if it
|
||||||
// parsed successfully and belongs to one of locally-served IP ranges as per
|
// was successfully parsed.
|
||||||
// RFC 6303.
|
|
||||||
unreversedReqIP net.IP
|
unreversedReqIP net.IP
|
||||||
// err is the error returned from a processing function.
|
// err is the error returned from a processing function.
|
||||||
err error
|
err error
|
||||||
@@ -67,6 +69,7 @@ const (
|
|||||||
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
||||||
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||||
ctx := &dnsContext{
|
ctx := &dnsContext{
|
||||||
|
srv: s,
|
||||||
proxyCtx: d,
|
proxyCtx: d,
|
||||||
result: &filtering.Result{},
|
result: &filtering.Result{},
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
@@ -81,19 +84,19 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
|||||||
// appropriate handler.
|
// appropriate handler.
|
||||||
mods := []modProcessFunc{
|
mods := []modProcessFunc{
|
||||||
s.processRecursion,
|
s.processRecursion,
|
||||||
s.processInitial,
|
processInitial,
|
||||||
s.processDetermineLocal,
|
s.processDetermineLocal,
|
||||||
s.processInternalHosts,
|
s.processInternalHosts,
|
||||||
s.processRestrictLocal,
|
s.processRestrictLocal,
|
||||||
s.processInternalIPAddrs,
|
s.processInternalIPAddrs,
|
||||||
s.processClientID,
|
s.processClientID,
|
||||||
s.processFilteringBeforeRequest,
|
processFilteringBeforeRequest,
|
||||||
s.processLocalPTR,
|
s.processLocalPTR,
|
||||||
s.processUpstream,
|
s.processUpstream,
|
||||||
s.processDNSSECAfterResponse,
|
processDNSSECAfterResponse,
|
||||||
s.processFilteringAfterResponse,
|
processFilteringAfterResponse,
|
||||||
s.ipset.process,
|
s.ipset.process,
|
||||||
s.processQueryLogsAndStats,
|
processQueryLogsAndStats,
|
||||||
}
|
}
|
||||||
for _, process := range mods {
|
for _, process := range mods {
|
||||||
r := process(ctx)
|
r := process(ctx)
|
||||||
@@ -132,7 +135,8 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform initial checks; process WHOIS & rDNS
|
// Perform initial checks; process WHOIS & rDNS
|
||||||
func (s *Server) processInitial(ctx *dnsContext) (rc resultCode) {
|
func processInitial(ctx *dnsContext) (rc resultCode) {
|
||||||
|
s := ctx.srv
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
if s.conf.AAAADisabled && d.Req.Question[0].Qtype == dns.TypeAAAA {
|
if s.conf.AAAADisabled && d.Req.Question[0].Qtype == dns.TypeAAAA {
|
||||||
_ = proxy.CheckDisabledAAAARequest(d, true)
|
_ = proxy.CheckDisabledAAAARequest(d, true)
|
||||||
@@ -151,9 +155,6 @@ func (s *Server) processInitial(ctx *dnsContext) (rc resultCode) {
|
|||||||
return resultCodeFinish
|
return resultCodeFinish
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.protectionEnabled = s.conf.ProtectionEnabled
|
|
||||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
|
||||||
|
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,32 +339,28 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restrict an access to local addresses for external clients. We also
|
// Restrict an access to local addresses for external clients. We also
|
||||||
// assume that all the DHCP leases we give are locally-served or at least
|
// assume that all the DHCP leases we give are locally-served or at
|
||||||
// don't need to be inaccessible externally.
|
// least don't need to be inaccessible externally.
|
||||||
if !s.subnetDetector.IsLocallyServedNetwork(ip) {
|
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
log.Debug("dns: addr %s is not from locally-served network", ip)
|
|
||||||
|
|
||||||
return resultCodeSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.isLocalClient {
|
if !ctx.isLocalClient {
|
||||||
log.Debug("dns: %q requests an internal ip", d.Addr)
|
log.Debug("dns: %q requests for internal ip", d.Addr)
|
||||||
d.Res = s.genNXDomain(req)
|
d.Res = s.genNXDomain(req)
|
||||||
|
|
||||||
// Do not even put into query log.
|
// Do not even put into query log.
|
||||||
return resultCodeFinish
|
return resultCodeFinish
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Do not perform unreversing ever again.
|
// Do not perform unreversing ever again.
|
||||||
ctx.unreversedReqIP = ip
|
ctx.unreversedReqIP = ip
|
||||||
|
|
||||||
// There is no need to filter request from external addresses since this
|
// Disable redundant filtering.
|
||||||
// code is only executed when the request is for locally-served ARPA
|
filterSetts := s.getClientRequestFilteringSettings(ctx)
|
||||||
// hostname so disable redundant filters.
|
filterSetts.ParentalEnabled = false
|
||||||
ctx.setts.ParentalEnabled = false
|
filterSetts.SafeBrowsingEnabled = false
|
||||||
ctx.setts.SafeBrowsingEnabled = false
|
filterSetts.SafeSearchEnabled = false
|
||||||
ctx.setts.SafeSearchEnabled = false
|
filterSetts.ServicesRules = nil
|
||||||
ctx.setts.ServicesRules = nil
|
ctx.setts = filterSetts
|
||||||
|
|
||||||
// Nothing to restrict.
|
// Nothing to restrict.
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
@@ -471,21 +468,29 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply filtering logic
|
// Apply filtering logic
|
||||||
func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
func processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
||||||
if ctx.proxyCtx.Res != nil {
|
s := ctx.srv
|
||||||
// Go on since the response is already set.
|
d := ctx.proxyCtx
|
||||||
return resultCodeSuccess
|
|
||||||
|
if d.Res != nil {
|
||||||
|
return resultCodeSuccess // response is already set - nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
s.serverLock.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.serverLock.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
if s.dnsFilter == nil {
|
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
||||||
|
if !ctx.protectionEnabled {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.setts == nil {
|
||||||
|
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if ctx.result, err = s.filterDNSRequest(ctx); err != nil {
|
ctx.result, err = s.filterDNSRequest(ctx)
|
||||||
|
if err != nil {
|
||||||
ctx.err = err
|
ctx.err = err
|
||||||
|
|
||||||
return resultCodeError
|
return resultCodeError
|
||||||
@@ -535,16 +540,8 @@ func (s *Server) processUpstream(ctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the request further since it wasn't filtered.
|
// request was not filtered so let it be processed further
|
||||||
|
if ctx.err = s.dnsProxy.Resolve(d); ctx.err != nil {
|
||||||
prx := s.proxy()
|
|
||||||
if prx == nil {
|
|
||||||
ctx.err = srvClosedErr
|
|
||||||
|
|
||||||
return resultCodeError
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.err = prx.Resolve(d); ctx.err != nil {
|
|
||||||
return resultCodeError
|
return resultCodeError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,11 +551,11 @@ func (s *Server) processUpstream(ctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process DNSSEC after response from upstream server
|
// Process DNSSEC after response from upstream server
|
||||||
func (s *Server) processDNSSECAfterResponse(ctx *dnsContext) (rc resultCode) {
|
func processDNSSECAfterResponse(ctx *dnsContext) (rc resultCode) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
|
|
||||||
// Don't process response if it's not from upstream servers.
|
if !ctx.responseFromUpstream || // don't process response if it's not from upstream servers
|
||||||
if !ctx.responseFromUpstream || !s.conf.EnableDNSSEC {
|
!ctx.srv.conf.EnableDNSSEC {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,52 +597,51 @@ func (s *Server) processDNSSECAfterResponse(ctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply filtering logic after we have received response from upstream servers
|
// Apply filtering logic after we have received response from upstream servers
|
||||||
func (s *Server) processFilteringAfterResponse(ctx *dnsContext) (rc resultCode) {
|
func processFilteringAfterResponse(ctx *dnsContext) (rc resultCode) {
|
||||||
|
s := ctx.srv
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
|
res := ctx.result
|
||||||
|
var err error
|
||||||
|
|
||||||
switch res := ctx.result; res.Reason {
|
switch res.Reason {
|
||||||
case filtering.NotFilteredAllowList:
|
case filtering.Rewritten,
|
||||||
// Go on.
|
|
||||||
case
|
|
||||||
filtering.Rewritten,
|
|
||||||
filtering.RewrittenRule:
|
filtering.RewrittenRule:
|
||||||
|
|
||||||
if len(ctx.origQuestion.Name) == 0 {
|
if len(ctx.origQuestion.Name) == 0 {
|
||||||
// origQuestion is set in case we get only CNAME without IP from
|
// origQuestion is set in case we get only CNAME without IP from rewrites table
|
||||||
// rewrites table.
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Req.Question[0], d.Res.Question[0] = ctx.origQuestion, ctx.origQuestion
|
d.Req.Question[0] = ctx.origQuestion
|
||||||
if len(d.Res.Answer) > 0 {
|
d.Res.Question[0] = ctx.origQuestion
|
||||||
answer := append([]dns.RR{s.genAnswerCNAME(d.Req, res.CanonName)}, d.Res.Answer...)
|
|
||||||
|
if len(d.Res.Answer) != 0 {
|
||||||
|
answer := []dns.RR{}
|
||||||
|
answer = append(answer, s.genAnswerCNAME(d.Req, res.CanonName))
|
||||||
|
answer = append(answer, d.Res.Answer...)
|
||||||
d.Res.Answer = answer
|
d.Res.Answer = answer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case filtering.NotFilteredAllowList:
|
||||||
|
// nothing
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Check the response only if the it's from an upstream. Don't check
|
if !ctx.protectionEnabled || // filters are disabled: there's nothing to check for
|
||||||
// the response if the protection is disabled since dnsrewrite rules
|
!ctx.responseFromUpstream { // only check response if it's from an upstream server
|
||||||
// aren't applied to it anyway.
|
|
||||||
if !ctx.protectionEnabled || !ctx.responseFromUpstream || s.dnsFilter == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
origResp2 := d.Res
|
||||||
origResp := d.Res
|
ctx.result, err = s.filterDNSResponse(ctx)
|
||||||
result, err := s.filterDNSResponse(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.err = err
|
ctx.err = err
|
||||||
|
|
||||||
return resultCodeError
|
return resultCodeError
|
||||||
}
|
}
|
||||||
|
if ctx.result != nil {
|
||||||
if result != nil {
|
ctx.origResp = origResp2 // matched by response
|
||||||
ctx.result = result
|
} else {
|
||||||
ctx.origResp = origResp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.result == nil {
|
|
||||||
ctx.result = &filtering.Result{}
|
ctx.result = &filtering.Result{}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -551,21 +551,6 @@ func (s *Server) IsRunning() bool {
|
|||||||
return s.isRunning
|
return s.isRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
// srvClosedErr is returned when the method can't complete without unacessible
|
|
||||||
// data from the closing server.
|
|
||||||
const srvClosedErr errors.Error = "server is closed"
|
|
||||||
|
|
||||||
// proxy returns a pointer to the current DNS proxy instance. If p is nil, the
|
|
||||||
// server is closing.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3655.
|
|
||||||
func (s *Server) proxy() (p *proxy.Proxy) {
|
|
||||||
s.serverLock.RLock()
|
|
||||||
defer s.serverLock.RUnlock()
|
|
||||||
|
|
||||||
return s.dnsProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconfigure applies the new configuration to the DNS server.
|
// Reconfigure applies the new configuration to the DNS server.
|
||||||
func (s *Server) Reconfigure(config *ServerConfig) error {
|
func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||||
s.serverLock.Lock()
|
s.serverLock.Lock()
|
||||||
@@ -596,8 +581,17 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
|||||||
|
|
||||||
// ServeHTTP is a HTTP handler method we use to provide DNS-over-HTTPS.
|
// ServeHTTP is a HTTP handler method we use to provide DNS-over-HTTPS.
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if prx := s.proxy(); prx != nil {
|
var p *proxy.Proxy
|
||||||
prx.ServeHTTP(w, r)
|
|
||||||
|
func() {
|
||||||
|
s.serverLock.RLock()
|
||||||
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
|
p = s.dnsProxy
|
||||||
|
}()
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
p.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
@@ -24,8 +23,6 @@ import (
|
|||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -46,7 +43,10 @@ func startDeferStop(t *testing.T, s *Server) {
|
|||||||
err := s.Start()
|
err := s.Start()
|
||||||
require.NoErrorf(t, err, "failed to start server: %s", err)
|
require.NoErrorf(t, err, "failed to start server: %s", err)
|
||||||
|
|
||||||
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
t.Cleanup(func() {
|
||||||
|
serr := s.Stop()
|
||||||
|
require.NoErrorf(t, serr, "dns server failed to stop: %s", serr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestServer(
|
func createTestServer(
|
||||||
@@ -107,7 +107,7 @@ func createServerTLSConfig(t *testing.T) (*tls.Config, []byte, []byte) {
|
|||||||
require.NoErrorf(t, err, "failed to generate serial number: %s", err)
|
require.NoErrorf(t, err, "failed to generate serial number: %s", err)
|
||||||
|
|
||||||
notBefore := time.Now()
|
notBefore := time.Now()
|
||||||
notAfter := notBefore.Add(5 * 365 * timeutil.Day)
|
notAfter := notBefore.Add(5 * 365 * time.Hour * 24)
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
@@ -907,7 +907,6 @@ func TestRewrite(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
f := filtering.New(c, nil)
|
f := filtering.New(c, nil)
|
||||||
f.SetEnabled(true)
|
|
||||||
|
|
||||||
snd, err := aghnet.NewSubnetDetector()
|
snd, err := aghnet.NewSubnetDetector()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -944,10 +943,9 @@ func TestRewrite(t *testing.T) {
|
|||||||
|
|
||||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||||
|
|
||||||
subTestFunc := func(t *testing.T) {
|
|
||||||
req := createTestMessageWithType("test.com.", dns.TypeA)
|
req := createTestMessageWithType("test.com.", dns.TypeA)
|
||||||
reply, eerr := dns.Exchange(req, addr.String())
|
reply, err := dns.Exchange(req, addr.String())
|
||||||
require.NoError(t, eerr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, reply.Answer, 1)
|
require.Len(t, reply.Answer, 1)
|
||||||
|
|
||||||
@@ -957,14 +955,14 @@ func TestRewrite(t *testing.T) {
|
|||||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(a.A))
|
assert.True(t, net.IP{1, 2, 3, 4}.Equal(a.A))
|
||||||
|
|
||||||
req = createTestMessageWithType("test.com.", dns.TypeAAAA)
|
req = createTestMessageWithType("test.com.", dns.TypeAAAA)
|
||||||
reply, eerr = dns.Exchange(req, addr.String())
|
reply, err = dns.Exchange(req, addr.String())
|
||||||
require.NoError(t, eerr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Empty(t, reply.Answer)
|
assert.Empty(t, reply.Answer)
|
||||||
|
|
||||||
req = createTestMessageWithType("alias.test.com.", dns.TypeA)
|
req = createTestMessageWithType("alias.test.com.", dns.TypeA)
|
||||||
reply, eerr = dns.Exchange(req, addr.String())
|
reply, err = dns.Exchange(req, addr.String())
|
||||||
require.NoError(t, eerr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, reply.Answer, 2)
|
require.Len(t, reply.Answer, 2)
|
||||||
|
|
||||||
@@ -972,8 +970,8 @@ func TestRewrite(t *testing.T) {
|
|||||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(reply.Answer[1].(*dns.A).A))
|
assert.True(t, net.IP{1, 2, 3, 4}.Equal(reply.Answer[1].(*dns.A).A))
|
||||||
|
|
||||||
req = createTestMessageWithType("my.alias.example.org.", dns.TypeA)
|
req = createTestMessageWithType("my.alias.example.org.", dns.TypeA)
|
||||||
reply, eerr = dns.Exchange(req, addr.String())
|
reply, err = dns.Exchange(req, addr.String())
|
||||||
require.NoError(t, eerr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// The original question is restored.
|
// The original question is restored.
|
||||||
require.Len(t, reply.Question, 1)
|
require.Len(t, reply.Question, 1)
|
||||||
@@ -986,16 +984,6 @@ func TestRewrite(t *testing.T) {
|
|||||||
assert.Equal(t, dns.TypeA, reply.Answer[1].Header().Rrtype)
|
assert.Equal(t, dns.TypeA, reply.Answer[1].Header().Rrtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, protect := range []bool{true, false} {
|
|
||||||
val := protect
|
|
||||||
conf := s.getDNSConfig()
|
|
||||||
conf.ProtectionEnabled = &val
|
|
||||||
s.setConfig(conf)
|
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("protection_is_%t", val), subTestFunc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func publicKey(priv interface{}) interface{} {
|
func publicKey(priv interface{}) interface{} {
|
||||||
switch k := priv.(type) {
|
switch k := priv.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
@@ -1047,7 +1035,9 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
|||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Cleanup(s.Close)
|
t.Cleanup(func() {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
|
||||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||||
req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR)
|
req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR)
|
||||||
@@ -1066,40 +1056,23 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPTRResponseFromHosts(t *testing.T) {
|
func TestPTRResponseFromHosts(t *testing.T) {
|
||||||
// Prepare test hosts file.
|
c := filtering.Config{
|
||||||
|
EtcHosts: &aghnet.EtcHostsContainer{},
|
||||||
const hostsFilename = "hosts"
|
|
||||||
|
|
||||||
testFS := fstest.MapFS{
|
|
||||||
hostsFilename: &fstest.MapFile{Data: []byte(`
|
|
||||||
127.0.0.1 host # comment
|
|
||||||
::1 localhost#comment
|
|
||||||
`)},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventsCalledCounter uint32
|
// Prepare test hosts file.
|
||||||
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
|
hf, err := os.CreateTemp("", "")
|
||||||
OnEvents: func() (e <-chan struct{}) {
|
|
||||||
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnAdd: func(name string) (err error) {
|
|
||||||
assert.Equal(t, hostsFilename, name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
|
||||||
}, hostsFilename)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
assert.Equal(t, uint32(1), atomic.LoadUint32(&eventsCalledCounter))
|
assert.NoError(t, hf.Close())
|
||||||
|
assert.NoError(t, os.Remove(hf.Name()))
|
||||||
})
|
})
|
||||||
|
|
||||||
flt := filtering.New(&filtering.Config{
|
_, _ = hf.WriteString(" 127.0.0.1 host # comment \n")
|
||||||
EtcHosts: hc,
|
_, _ = hf.WriteString(" ::1 localhost#comment \n")
|
||||||
}, nil)
|
|
||||||
flt.SetEnabled(true)
|
c.EtcHosts.Init(hf.Name())
|
||||||
|
t.Cleanup(c.EtcHosts.Close)
|
||||||
|
|
||||||
var snd *aghnet.SubnetDetector
|
var snd *aghnet.SubnetDetector
|
||||||
snd, err = aghnet.NewSubnetDetector()
|
snd, err = aghnet.NewSubnetDetector()
|
||||||
@@ -1109,7 +1082,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
|||||||
var s *Server
|
var s *Server
|
||||||
s, err = NewServer(DNSCreateParams{
|
s, err = NewServer(DNSCreateParams{
|
||||||
DHCPServer: &testDHCP{},
|
DHCPServer: &testDHCP{},
|
||||||
DNSFilter: flt,
|
DNSFilter: filtering.New(&c, nil),
|
||||||
SubnetDetector: snd,
|
SubnetDetector: snd,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1117,20 +1090,23 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
|||||||
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
||||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||||
|
s.conf.FilteringConfig.ProtectionEnabled = true
|
||||||
|
|
||||||
err = s.Prepare(nil)
|
err = s.Prepare(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(s.Close)
|
|
||||||
|
|
||||||
subTestFunc := func(t *testing.T) {
|
t.Cleanup(func() {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
|
||||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||||
req := createTestMessageWithType("1.0.0.127.in-addr.arpa.", dns.TypePTR)
|
req := createTestMessageWithType("1.0.0.127.in-addr.arpa.", dns.TypePTR)
|
||||||
|
|
||||||
resp, eerr := dns.Exchange(req, addr.String())
|
resp, err := dns.Exchange(req, addr.String())
|
||||||
require.NoError(t, eerr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, resp.Answer, 1)
|
require.Len(t, resp.Answer, 1)
|
||||||
|
|
||||||
@@ -1142,16 +1118,6 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
|||||||
assert.Equal(t, "host.", ptr.Ptr)
|
assert.Equal(t, "host.", ptr.Ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, protect := range []bool{true, false} {
|
|
||||||
val := protect
|
|
||||||
conf := s.getDNSConfig()
|
|
||||||
conf.ProtectionEnabled = &val
|
|
||||||
s.setConfig(conf)
|
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("protection_is_%t", val), subTestFunc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewServer(t *testing.T) {
|
func TestNewServer(t *testing.T) {
|
||||||
// TODO(a.garipov): Consider moving away from the text-based error
|
// TODO(a.garipov): Consider moving away from the text-based error
|
||||||
// checks and onto a more structured approach.
|
// checks and onto a more structured approach.
|
||||||
@@ -1187,7 +1153,12 @@ func TestNewServer(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) {
|
||||||
_, err := NewServer(tc.in)
|
_, err := NewServer(tc.in)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
if tc.wantErrMsg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
assert.Equal(t, dns.RcodeNameError, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeNameError, d.Res.Rcode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,8 +72,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
assert.Empty(t, d.Res.Answer)
|
assert.Empty(t, d.Res.Answer)
|
||||||
})
|
})
|
||||||
@@ -84,8 +83,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -98,8 +96,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -112,8 +109,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -126,8 +122,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -140,8 +135,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -158,8 +152,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -178,8 +171,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
@@ -198,8 +190,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
d := &proxy.DNSContext{}
|
d := &proxy.DNSContext{}
|
||||||
|
|
||||||
err := srv.filterDNSRewrite(req, res, d)
|
err := srv.filterDNSRewrite(req, res, d)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)
|
||||||
|
|
||||||
require.Len(t, d.Res.Answer, 1)
|
require.Len(t, d.Res.Answer, 1)
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ func (s *Server) beforeRequestHandler(
|
|||||||
// the client's IP address and ID, if any, from ctx.
|
// the client's IP address and ID, if any, from ctx.
|
||||||
func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *filtering.Settings {
|
func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *filtering.Settings {
|
||||||
setts := s.dnsFilter.GetConfig()
|
setts := s.dnsFilter.GetConfig()
|
||||||
setts.ProtectionEnabled = ctx.protectionEnabled
|
|
||||||
if s.conf.FilterHandler != nil {
|
if s.conf.FilterHandler != nil {
|
||||||
ip, _ := netutil.IPAndPortFromAddr(ctx.proxyCtx.Addr)
|
ip, _ := netutil.IPAndPortFromAddr(ctx.proxyCtx.Addr)
|
||||||
s.conf.FilterHandler(ip, ctx.clientID, &setts)
|
s.conf.FilterHandler(ip, ctx.clientID, &setts)
|
||||||
@@ -66,23 +65,42 @@ func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *filtering.S
|
|||||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
req := d.Req
|
req := d.Req
|
||||||
q := req.Question[0]
|
host := strings.TrimSuffix(req.Question[0].Name, ".")
|
||||||
host := strings.TrimSuffix(q.Name, ".")
|
res, err := s.dnsFilter.CheckHost(host, req.Question[0].Qtype, ctx.setts)
|
||||||
res, err := s.dnsFilter.CheckHost(host, q.Qtype, ctx.setts)
|
if err != nil {
|
||||||
switch {
|
// Return immediately if there's an error
|
||||||
case err != nil:
|
return nil, fmt.Errorf("filtering failed to check host %q: %w", host, err)
|
||||||
return nil, fmt.Errorf("failed to check host %q: %w", host, err)
|
} else if res.IsFiltered {
|
||||||
case res.IsFiltered:
|
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text)
|
||||||
log.Tracef("host %q is filtered, reason %q, rule: %q", host, res.Reason, res.Rules[0].Text)
|
|
||||||
d.Res = s.genDNSFilterMessage(d, &res)
|
d.Res = s.genDNSFilterMessage(d, &res)
|
||||||
case res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
|
} else if res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
|
||||||
res.CanonName != "" &&
|
res.CanonName != "" &&
|
||||||
len(res.IPList) == 0:
|
len(res.IPList) == 0 {
|
||||||
// Resolve the new canonical name, not the original host name. The
|
// Resolve the new canonical name, not the original host
|
||||||
// original question is readded in processFilteringAfterResponse.
|
// name. The original question is readded in
|
||||||
ctx.origQuestion = q
|
// processFilteringAfterResponse.
|
||||||
|
ctx.origQuestion = req.Question[0]
|
||||||
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||||
case res.Reason == filtering.Rewritten:
|
} else if res.Reason == filtering.RewrittenAutoHosts && len(res.ReverseHosts) != 0 {
|
||||||
|
resp := s.makeResponse(req)
|
||||||
|
for _, h := range res.ReverseHosts {
|
||||||
|
hdr := dns.RR_Header{
|
||||||
|
Name: req.Question[0].Name,
|
||||||
|
Rrtype: dns.TypePTR,
|
||||||
|
Ttl: s.conf.BlockedResponseTTL,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := &dns.PTR{
|
||||||
|
Hdr: hdr,
|
||||||
|
Ptr: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Answer = append(resp.Answer, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Res = resp
|
||||||
|
} else if res.Reason.In(filtering.Rewritten, filtering.RewrittenAutoHosts) {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
|
|
||||||
name := host
|
name := host
|
||||||
@@ -92,12 +110,11 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range res.IPList {
|
for _, ip := range res.IPList {
|
||||||
switch q.Qtype {
|
if req.Question[0].Qtype == dns.TypeA {
|
||||||
case dns.TypeA:
|
|
||||||
a := s.genAnswerA(req, ip.To4())
|
a := s.genAnswerA(req, ip.To4())
|
||||||
a.Hdr.Name = dns.Fqdn(name)
|
a.Hdr.Name = dns.Fqdn(name)
|
||||||
resp.Answer = append(resp.Answer, a)
|
resp.Answer = append(resp.Answer, a)
|
||||||
case dns.TypeAAAA:
|
} else if req.Question[0].Qtype == dns.TypeAAAA {
|
||||||
a := s.genAnswerAAAA(req, ip)
|
a := s.genAnswerAAAA(req, ip)
|
||||||
a.Hdr.Name = dns.Fqdn(name)
|
a.Hdr.Name = dns.Fqdn(name)
|
||||||
resp.Answer = append(resp.Answer, a)
|
resp.Answer = append(resp.Answer, a)
|
||||||
@@ -105,8 +122,9 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.Res = resp
|
d.Res = resp
|
||||||
case res.Reason.In(filtering.RewrittenRule, filtering.RewrittenAutoHosts):
|
} else if res.Reason == filtering.RewrittenRule {
|
||||||
if err = s.filterDNSRewrite(req, res, d); err != nil {
|
err = s.filterDNSRewrite(req, res, d)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +179,6 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*filtering.Result, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
host = strings.TrimSuffix(host, ".")
|
|
||||||
res, err := s.checkHostRules(host, d.Req.Question[0].Qtype, ctx.setts)
|
res, err := s.checkHostRules(host, d.Req.Question[0].Qtype, ctx.setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -39,7 +38,9 @@ func loadTestData(t *testing.T, casesFileName string, cases interface{}) {
|
|||||||
var f *os.File
|
var f *os.File
|
||||||
f, err := os.Open(filepath.Join("testdata", casesFileName))
|
f, err := os.Open(filepath.Join("testdata", casesFileName))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, f.Close)
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, f.Close())
|
||||||
|
})
|
||||||
|
|
||||||
err = json.NewDecoder(f).Decode(cases)
|
err = json.NewDecoder(f).Decode(cases)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -68,8 +69,10 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
|||||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||||
s.sysResolvers = &fakeSystemResolvers{}
|
s.sysResolvers = &fakeSystemResolvers{}
|
||||||
|
|
||||||
require.NoError(t, s.Start())
|
require.Nil(t, s.Start())
|
||||||
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
t.Cleanup(func() {
|
||||||
|
require.Nil(t, s.Stop())
|
||||||
|
})
|
||||||
|
|
||||||
defaultConf := s.conf
|
defaultConf := s.conf
|
||||||
|
|
||||||
@@ -144,8 +147,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
defaultConf := s.conf
|
defaultConf := s.conf
|
||||||
|
|
||||||
err := s.Start()
|
err := s.Start()
|
||||||
assert.NoError(t, err)
|
assert.Nil(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
t.Cleanup(func() {
|
||||||
|
assert.Nil(t, s.Stop())
|
||||||
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
@@ -216,12 +221,14 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(func() { s.conf = defaultConf })
|
t.Cleanup(func() {
|
||||||
|
s.conf = defaultConf
|
||||||
|
})
|
||||||
|
|
||||||
rBody := io.NopCloser(bytes.NewReader(caseData.Req))
|
rBody := io.NopCloser(bytes.NewReader(caseData.Req))
|
||||||
var r *http.Request
|
var r *http.Request
|
||||||
r, err = http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
r, err = http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
||||||
require.NoError(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
s.handleSetConfig(w, r)
|
s.handleSetConfig(w, r)
|
||||||
assert.Equal(t, tc.wantSet, strings.TrimSuffix(w.Body.String(), "\n"))
|
assert.Equal(t, tc.wantSet, strings.TrimSuffix(w.Body.String(), "\n"))
|
||||||
|
|||||||
@@ -249,17 +249,9 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
|||||||
Req: &replReq,
|
Req: &replReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
prx := s.proxy()
|
err := s.dnsProxy.Resolve(newContext)
|
||||||
if prx == nil {
|
|
||||||
log.Debug("dns: %s", srvClosedErr)
|
|
||||||
|
|
||||||
return s.genServerFailure(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := prx.Resolve(newContext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't look up replacement host %q: %s", newAddr, err)
|
log.Printf("Couldn't look up replacement host %q: %s", newAddr, err)
|
||||||
|
|
||||||
return s.genServerFailure(request)
|
return s.genServerFailure(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user