mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-15 09:58:42 -07:00
all: sync with master; upd chlog
This commit is contained in:
parent
b21e19a223
commit
82ab4328d4
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
'name': 'build'
|
'name': 'build'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.20.10'
|
'GO_VERSION': '1.20.11'
|
||||||
'NODE_VERSION': '16'
|
'NODE_VERSION': '16'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
'name': 'lint'
|
'name': 'lint'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.20.10'
|
'GO_VERSION': '1.20.11'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
'push':
|
'push':
|
||||||
|
69
CHANGELOG.md
69
CHANGELOG.md
@ -14,11 +14,11 @@ and this project adheres to
|
|||||||
<!--
|
<!--
|
||||||
## [v0.108.0] - TBA
|
## [v0.108.0] - TBA
|
||||||
|
|
||||||
## [v0.107.41] - 2023-11-01 (APPROX.)
|
## [v0.107.42] - 2023-12-06 (APPROX.)
|
||||||
|
|
||||||
See also the [v0.107.41 GitHub milestone][ms-v0.107.41].
|
See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
|
||||||
|
|
||||||
[ms-v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/milestone/76?closed=1
|
[ms-v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/milestone/76?closed=1
|
||||||
|
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
@ -29,6 +29,62 @@ NOTE: Add new changes ABOVE THIS COMMENT.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.107.41] - 2023-11-13
|
||||||
|
|
||||||
|
See also the [v0.107.41 GitHub milestone][ms-v0.107.41].
|
||||||
|
|
||||||
|
[ms-v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/milestone/76?closed=1
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Go version has been updated to prevent the possibility of exploiting the
|
||||||
|
CVE-2023-45283 and CVE-2023-45284 Go vulnerabilities fixed in
|
||||||
|
[Go 1.20.11][go-1.20.11].
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Ability to specify subnet lengths for IPv4 and IPv6 addresses, used for rate
|
||||||
|
limiting requests, in the configuration file ([#6368]).
|
||||||
|
- Ability to specify multiple domain specific upstreams per line, e.g.
|
||||||
|
`[/domain1/../domain2/]upstream1 upstream2 .. upstreamN` ([#4977]).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The height of ready-to-use filter lists has been increased ([#6358]).
|
||||||
|
- Improved authentication failure logging ([#6357]).
|
||||||
|
|
||||||
|
#### Configuration Changes
|
||||||
|
|
||||||
|
- New properties `dns.ratelimit_subnet_len_ipv4` and
|
||||||
|
`dns.ratelimit_subnet_len_ipv6` in the configuration file ([#6368]).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Missing timezone on schedule form submission ([#6401]).
|
||||||
|
- Average request processing time calculation ([#6220]).
|
||||||
|
- Redundant shortening long client names in the Top Clients table ([#6338]).
|
||||||
|
- Scrolling column headers in the tables ([#6337]).
|
||||||
|
- `$important,dnsrewrite` rules do not take precedence over allowlist rules
|
||||||
|
([#6204]).
|
||||||
|
- Dark mode DNS rewrite background ([#6329]).
|
||||||
|
- Issues with QUIC and HTTP/3 upstreams on Linux ([#6335]).
|
||||||
|
|
||||||
|
[#4977]: https://github.com/AdguardTeam/AdGuardHome/issues/4977
|
||||||
|
[#6204]: https://github.com/AdguardTeam/AdGuardHome/issues/6204
|
||||||
|
[#6220]: https://github.com/AdguardTeam/AdGuardHome/issues/6220
|
||||||
|
[#6329]: https://github.com/AdguardTeam/AdGuardHome/issues/6329
|
||||||
|
[#6335]: https://github.com/AdguardTeam/AdGuardHome/issues/6335
|
||||||
|
[#6337]: https://github.com/AdguardTeam/AdGuardHome/issues/6337
|
||||||
|
[#6338]: https://github.com/AdguardTeam/AdGuardHome/issues/6338
|
||||||
|
[#6357]: https://github.com/AdguardTeam/AdGuardHome/issues/6357
|
||||||
|
[#6358]: https://github.com/AdguardTeam/AdGuardHome/issues/6358
|
||||||
|
[#6368]: https://github.com/AdguardTeam/AdGuardHome/issues/6368
|
||||||
|
[#6401]: https://github.com/AdguardTeam/AdGuardHome/issues/6401
|
||||||
|
|
||||||
|
[go-1.20.11]: https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY/m/d-jSKR_jBwAJ
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.107.40] - 2023-10-18
|
## [v0.107.40] - 2023-10-18
|
||||||
|
|
||||||
See also the [v0.107.40 GitHub milestone][ms-v0.107.40].
|
See also the [v0.107.40 GitHub milestone][ms-v0.107.40].
|
||||||
@ -2557,11 +2613,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...HEAD
|
||||||
[v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...v0.107.41
|
[v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...v0.107.42
|
||||||
-->
|
-->
|
||||||
|
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...HEAD
|
||||||
|
[v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...v0.107.41
|
||||||
[v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...v0.107.40
|
[v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...v0.107.40
|
||||||
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39
|
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39
|
||||||
[v0.107.38]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...v0.107.38
|
[v0.107.38]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...v0.107.38
|
||||||
|
@ -201,7 +201,7 @@ opinion, this cannot be legitimately counted as a Pi-Hole's feature.
|
|||||||
| Cross-platform | ✅ | ❌ (not natively, only via Docker) |
|
| Cross-platform | ✅ | ❌ (not natively, only via Docker) |
|
||||||
| Running as a DNS-over-HTTPS or DNS-over-TLS server | ✅ | ❌ (requires additional software) |
|
| Running as a DNS-over-HTTPS or DNS-over-TLS server | ✅ | ❌ (requires additional software) |
|
||||||
| Blocking phishing and malware domains | ✅ | ❌ (requires non-default blocklists) |
|
| Blocking phishing and malware domains | ✅ | ❌ (requires non-default blocklists) |
|
||||||
| Parental control (blocking adult domains) | ✅ | ❌ |
|
| Parental control (blocking adult domains) | ✅ | ❌ (requires non-default blocklists) |
|
||||||
| Force Safe search on search engines | ✅ | ❌ |
|
| Force Safe search on search engines | ✅ | ❌ |
|
||||||
| Per-client (device) configuration | ✅ | ✅ |
|
| Per-client (device) configuration | ✅ | ✅ |
|
||||||
| Access settings (choose who can use AGH DNS) | ✅ | ❌ |
|
| Access settings (choose who can use AGH DNS) | ✅ | ❌ |
|
||||||
|
@ -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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Build frontend':
|
- 'Build frontend':
|
||||||
@ -272,7 +272,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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final
|
# release-vX.Y.Z branches are the branches from which the actual final
|
||||||
# release is built.
|
# release is built.
|
||||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||||
@ -287,4 +287,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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
|
@ -10,7 +10,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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
'snapcraftChannel': 'edge'
|
'snapcraftChannel': 'edge'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
@ -191,7 +191,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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
'snapcraftChannel': 'beta'
|
'snapcraftChannel': 'beta'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final
|
# release-vX.Y.Z branches are the branches from which the actual final
|
||||||
# release is built.
|
# release is built.
|
||||||
@ -207,5 +207,5 @@
|
|||||||
# are the ones that actually get released.
|
# are the ones that actually get released.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'release'
|
'channel': 'release'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
'snapcraftChannel': 'candidate'
|
'snapcraftChannel': 'candidate'
|
||||||
|
@ -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:7.4'
|
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Tests':
|
- 'Tests':
|
||||||
|
@ -143,7 +143,6 @@
|
|||||||
"enforced_save_search": "Ужыты бяспечны пошук",
|
"enforced_save_search": "Ужыты бяспечны пошук",
|
||||||
"number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук",
|
"number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук",
|
||||||
"average_processing_time": "Сярэдні час апрацоўкі запыту",
|
"average_processing_time": "Сярэдні час апрацоўкі запыту",
|
||||||
"processing_time": "Час апрацоўкі",
|
|
||||||
"average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах",
|
"average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах",
|
||||||
"block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў",
|
"block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў",
|
||||||
"filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «<a>Фільтрах</a>».",
|
"filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «<a>Фільтрах</a>».",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Nastavení klienta",
|
"client_settings": "Nastavení klienta",
|
||||||
"example_upstream_reserved": "odchozí DNS připojení <0>pro konkrétní doménu(y)</0>;",
|
"example_upstream_reserved": "odchozí DNS připojení <0>pro konkrétní doménu(y)</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "více odchozích připojení <0>pro konkrétní domény</0>;",
|
||||||
"example_upstream_comment": "komentář.",
|
"example_upstream_comment": "komentář.",
|
||||||
"upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
|
"upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
|
||||||
"parallel_requests": "Paralelní požadavky",
|
"parallel_requests": "Paralelní požadavky",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Vynucené bezpečné vyhledávání",
|
"enforced_save_search": "Vynucené bezpečné vyhledávání",
|
||||||
"number_of_dns_query_to_safe_search": "Počet požadavků DNS na vyhledávače, při kterých bylo vynucené bezpečné vyhledávání",
|
"number_of_dns_query_to_safe_search": "Počet požadavků DNS na vyhledávače, při kterých bylo vynucené bezpečné vyhledávání",
|
||||||
"average_processing_time": "Průměrný čas zpracování",
|
"average_processing_time": "Průměrný čas zpracování",
|
||||||
"processing_time": "Doba zpracování",
|
"average_upstream_response_time": "Průměrná doba odezvy odchozích připojení",
|
||||||
|
"response_time": "Čas odezvy",
|
||||||
"average_processing_time_hint": "Průměrný čas zpracování požadavků DNS v milisekundách",
|
"average_processing_time_hint": "Průměrný čas zpracování požadavků DNS v milisekundách",
|
||||||
"block_domain_use_filters_and_hosts": "Blokovat domény pomocí filtrů a seznamů adres",
|
"block_domain_use_filters_and_hosts": "Blokovat domény pomocí filtrů a seznamů adres",
|
||||||
"filters_block_toggle_hint": "Pravidla blokování můžete nastavit v nastavení <a>Filtry</a>.",
|
"filters_block_toggle_hint": "Pravidla blokování můžete nastavit v nastavení <a>Filtry</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Klientindstillinger",
|
"client_settings": "Klientindstillinger",
|
||||||
"example_upstream_reserved": "en upstream <0>for bestemte domæner</0>;",
|
"example_upstream_reserved": "en upstream <0>for bestemte domæner</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "flere upstreams <0>til bestemte domæner</0>;",
|
||||||
"example_upstream_comment": "en kommentaren.",
|
"example_upstream_comment": "en kommentaren.",
|
||||||
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
|
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
|
||||||
"parallel_requests": "Parallelle forespørgsler",
|
"parallel_requests": "Parallelle forespørgsler",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Håndhævet sikker søgning",
|
"enforced_save_search": "Håndhævet sikker søgning",
|
||||||
"number_of_dns_query_to_safe_search": "Antallet af DNS-forespørgsler til søgemaskiner, hvor Sikker Søgning blev håndhævet",
|
"number_of_dns_query_to_safe_search": "Antallet af DNS-forespørgsler til søgemaskiner, hvor Sikker Søgning blev håndhævet",
|
||||||
"average_processing_time": "Gennemsnitlig behandlingstid",
|
"average_processing_time": "Gennemsnitlig behandlingstid",
|
||||||
"processing_time": "Behandlingstid",
|
"average_upstream_response_time": "Gennemsnitlig upstream-responstid",
|
||||||
|
"response_time": "Responstid",
|
||||||
"average_processing_time_hint": "Gennemsnitlig behandlingstid i millisekunder af DNS-forespørgsel",
|
"average_processing_time_hint": "Gennemsnitlig behandlingstid i millisekunder af DNS-forespørgsel",
|
||||||
"block_domain_use_filters_and_hosts": "Blokér domæner vha. filtre og værtsfiler",
|
"block_domain_use_filters_and_hosts": "Blokér domæner vha. filtre og værtsfiler",
|
||||||
"filters_block_toggle_hint": "Du kan opsætte blokeringsregler i <a>Filterindstillingerne</a>.",
|
"filters_block_toggle_hint": "Du kan opsætte blokeringsregler i <a>Filterindstillingerne</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Client-Einstellungen",
|
"client_settings": "Client-Einstellungen",
|
||||||
"example_upstream_reserved": "ein Upstream <0>für bestimmte Domains</0>;",
|
"example_upstream_reserved": "ein Upstream <0>für bestimmte Domains</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "mehrere Upstreams <0>für bestimmte Domains</0>;",
|
||||||
"example_upstream_comment": "ein Kommentar.",
|
"example_upstream_comment": "ein Kommentar.",
|
||||||
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
|
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
|
||||||
"parallel_requests": "Paralleles Abfragen",
|
"parallel_requests": "Paralleles Abfragen",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Sichere Suche erzwungen",
|
"enforced_save_search": "Sichere Suche erzwungen",
|
||||||
"number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen Sichere Suche für Suchanfragen erzwungen wurde",
|
"number_of_dns_query_to_safe_search": "Anzahl der DNS-Anfragen bei denen Sichere Suche für Suchanfragen erzwungen wurde",
|
||||||
"average_processing_time": "Durchschnittliche Bearbeitungsdauer",
|
"average_processing_time": "Durchschnittliche Bearbeitungsdauer",
|
||||||
"processing_time": "Verarbeitungszeit",
|
"average_upstream_response_time": "Durchschnittliche Upstream-Antwortzeit",
|
||||||
|
"response_time": "Antwortzeit",
|
||||||
"average_processing_time_hint": "Durchschnittliche Zeit in Millisekunden zur Bearbeitung von DNS-Anfragen",
|
"average_processing_time_hint": "Durchschnittliche Zeit in Millisekunden zur Bearbeitung von DNS-Anfragen",
|
||||||
"block_domain_use_filters_and_hosts": "Domains durch Filter und Host-Dateien sperren",
|
"block_domain_use_filters_and_hosts": "Domains durch Filter und Host-Dateien sperren",
|
||||||
"filters_block_toggle_hint": "Sie können Blockierregeln in den <a>Filter</a>einstellungen erstellen.",
|
"filters_block_toggle_hint": "Sie können Blockierregeln in den <a>Filter</a>einstellungen erstellen.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Client settings",
|
"client_settings": "Client settings",
|
||||||
"example_upstream_reserved": "an upstream <0>for specific domains</0>;",
|
"example_upstream_reserved": "an upstream <0>for specific domains</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "multiple upstreams <0>for specific domains</0>;",
|
||||||
"example_upstream_comment": "a comment.",
|
"example_upstream_comment": "a comment.",
|
||||||
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
|
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
|
||||||
"parallel_requests": "Parallel requests",
|
"parallel_requests": "Parallel requests",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Enforced safe search",
|
"enforced_save_search": "Enforced safe search",
|
||||||
"number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced",
|
"number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced",
|
||||||
"average_processing_time": "Average processing time",
|
"average_processing_time": "Average processing time",
|
||||||
"processing_time": "Processing time",
|
"average_upstream_response_time": "Average upstream response time",
|
||||||
|
"response_time": "Response time",
|
||||||
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
|
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
|
||||||
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
|
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
|
||||||
"filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.",
|
"filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Configuración de clientes",
|
"client_settings": "Configuración de clientes",
|
||||||
"example_upstream_reserved": "un DNS de subida <0>para un dominio específico</0>.",
|
"example_upstream_reserved": "un DNS de subida <0>para un dominio específico</0>.",
|
||||||
|
"example_multiple_upstreams_reserved": "múltiples upstreams <0>para dominios específicos</0>;",
|
||||||
"example_upstream_comment": "un comentario.",
|
"example_upstream_comment": "un comentario.",
|
||||||
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
|
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
|
||||||
"parallel_requests": "Consultas paralelas",
|
"parallel_requests": "Consultas paralelas",
|
||||||
@ -8,9 +9,9 @@
|
|||||||
"load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.",
|
"load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.",
|
||||||
"bootstrap_dns": "Servidores DNS de arranque",
|
"bootstrap_dns": "Servidores DNS de arranque",
|
||||||
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
|
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
|
||||||
"fallback_dns_title": "Servidores DNS de fallback",
|
"fallback_dns_title": "Servidores DNS alternativos",
|
||||||
"fallback_dns_desc": "La lista de DNS de fallback serán usadas cuando los servidores de upstream de DNS no respondan. La sintaxis es la misma que en los principales del campo anterior.",
|
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
|
||||||
"fallback_dns_placeholder": "Ingresa un servidor de DNS alternativo por línea",
|
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
|
||||||
"local_ptr_title": "Servidores DNS inversos y privados",
|
"local_ptr_title": "Servidores DNS inversos y privados",
|
||||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||||
"local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.",
|
"local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.",
|
||||||
@ -131,8 +132,8 @@
|
|||||||
"top_clients": "Clientes más frecuentes",
|
"top_clients": "Clientes más frecuentes",
|
||||||
"no_clients_found": "No se han encontrado clientes",
|
"no_clients_found": "No se han encontrado clientes",
|
||||||
"general_statistics": "Estadísticas generales",
|
"general_statistics": "Estadísticas generales",
|
||||||
"top_upstreams": "Mejores upstreams",
|
"top_upstreams": "DNS de subida más frecuentes",
|
||||||
"no_upstreams_data_found": "No se han encontrado datos de upstreams",
|
"no_upstreams_data_found": "No se han encontrado datos de DNS de subida",
|
||||||
"number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día",
|
"number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día",
|
||||||
"number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días",
|
"number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días",
|
||||||
"number_of_dns_query_hours": "Número de consultas DNS procesadas durante la última {{count}} hora",
|
"number_of_dns_query_hours": "Número de consultas DNS procesadas durante la última {{count}} hora",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Búsquedas seguras forzadas",
|
"enforced_save_search": "Búsquedas seguras forzadas",
|
||||||
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
|
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
|
||||||
"average_processing_time": "Tiempo promedio de procesamiento",
|
"average_processing_time": "Tiempo promedio de procesamiento",
|
||||||
"processing_time": "Tiempo de procesamiento",
|
"average_upstream_response_time": "Tiempo promedio de respuesta upstream",
|
||||||
|
"response_time": "Tiempo de respuesta",
|
||||||
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS",
|
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
|
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
|
||||||
"filters_block_toggle_hint": "Puedes configurar las reglas de bloqueo en la configuración de <a>filtros</a>.",
|
"filters_block_toggle_hint": "Puedes configurar las reglas de bloqueo en la configuración de <a>filtros</a>.",
|
||||||
@ -168,7 +170,7 @@
|
|||||||
"upstream_dns_configured_in_file": "Configurado en {{path}}",
|
"upstream_dns_configured_in_file": "Configurado en {{path}}",
|
||||||
"test_upstream_btn": "Probar DNS de subida",
|
"test_upstream_btn": "Probar DNS de subida",
|
||||||
"upstreams": "DNS de subida",
|
"upstreams": "DNS de subida",
|
||||||
"upstream": "Upstream",
|
"upstream": "DNS de subida",
|
||||||
"apply_btn": "Aplicar",
|
"apply_btn": "Aplicar",
|
||||||
"disabled_filtering_toast": "Filtrado deshabilitado",
|
"disabled_filtering_toast": "Filtrado deshabilitado",
|
||||||
"enabled_filtering_toast": "Filtrado habilitado",
|
"enabled_filtering_toast": "Filtrado habilitado",
|
||||||
@ -677,21 +679,21 @@
|
|||||||
"disable_for_hours": "Por {{count}} hora",
|
"disable_for_hours": "Por {{count}} hora",
|
||||||
"disable_for_hours_plural": "Por {{count}} horas",
|
"disable_for_hours_plural": "Por {{count}} horas",
|
||||||
"disable_until_tomorrow": "Hasta mañana",
|
"disable_until_tomorrow": "Hasta mañana",
|
||||||
"disable_notify_for_seconds": "Desactivar la protección por {{count}} segundo",
|
"disable_notify_for_seconds": "Deshabilitar protección por {{count}} segundo",
|
||||||
"disable_notify_for_seconds_plural": "Desactivar la protección por {{count}} segundos",
|
"disable_notify_for_seconds_plural": "Deshabilitar protección por {{count}} segundos",
|
||||||
"disable_notify_for_minutes": "Desactivar la protección por {{count}} minuto",
|
"disable_notify_for_minutes": "Deshabilitar protección por {{count}} minuto",
|
||||||
"disable_notify_for_minutes_plural": "Desactivar la protección por {{count}} minutos",
|
"disable_notify_for_minutes_plural": "Deshabilitar protección por {{count}} minutos",
|
||||||
"disable_notify_for_hours": "Desactivar la protección por {{count}} hora",
|
"disable_notify_for_hours": "Deshabilitar protección por {{count}} hora",
|
||||||
"disable_notify_for_hours_plural": "Desactivar la protección por {{count}} horas",
|
"disable_notify_for_hours_plural": "Deshabilitar protección por {{count}} horas",
|
||||||
"disable_notify_until_tomorrow": "Desactivar la protección hasta mañana",
|
"disable_notify_until_tomorrow": "Deshabilitar protección hasta mañana",
|
||||||
"enable_protection_timer": "La protección se activará en {{time}}",
|
"enable_protection_timer": "La protección se habilitará a las {{time}}",
|
||||||
"custom_retention_input": "Ingresa la retención en horas",
|
"custom_retention_input": "Ingresa la retención en horas",
|
||||||
"custom_rotation_input": "Ingresa la rotación en horas",
|
"custom_rotation_input": "Ingresa la rotación en horas",
|
||||||
"protection_section_label": "Protección",
|
"protection_section_label": "Protección",
|
||||||
"log_and_stats_section_label": "Registro de consultas y estadísticas",
|
"log_and_stats_section_label": "Registro de consultas y estadísticas",
|
||||||
"ignore_query_log": "Ignorar este cliente en el registro de consultas",
|
"ignore_query_log": "Ignorar este cliente en el registro de consultas",
|
||||||
"ignore_statistics": "Ignorar este cliente en las estadísticas",
|
"ignore_statistics": "Ignorar este cliente en las estadísticas",
|
||||||
"schedule_services": "Pausar el servicio de bloqueo",
|
"schedule_services": "Pausar servicio de bloqueo",
|
||||||
"schedule_services_desc": "Configura el horario programado de pausa del servicio de bloqueo",
|
"schedule_services_desc": "Configura el horario programado de pausa del servicio de bloqueo",
|
||||||
"schedule_services_desc_client": "Configurar el horario programado de pausa del bloqueo de servicio filtrado para este cliente",
|
"schedule_services_desc_client": "Configurar el horario programado de pausa del bloqueo de servicio filtrado para este cliente",
|
||||||
"schedule_desc": "Establecer periodos de inactividad para servicios bloqueados",
|
"schedule_desc": "Establecer periodos de inactividad para servicios bloqueados",
|
||||||
@ -701,7 +703,7 @@
|
|||||||
"schedule_current_timezone": "Zona horaria actual: {{value}}",
|
"schedule_current_timezone": "Zona horaria actual: {{value}}",
|
||||||
"schedule_time_all_day": "Todo el dia",
|
"schedule_time_all_day": "Todo el dia",
|
||||||
"schedule_modal_description": "Este horario sustituirá cualquier horario existente para el mismo día de la semana. Cada día de la semana solo puede tener un periodo de inactividad.",
|
"schedule_modal_description": "Este horario sustituirá cualquier horario existente para el mismo día de la semana. Cada día de la semana solo puede tener un periodo de inactividad.",
|
||||||
"schedule_modal_time_off": "Detener el servicio de bloqueo:",
|
"schedule_modal_time_off": "Detener servicio de bloqueo:",
|
||||||
"schedule_new": "Nuevo horario",
|
"schedule_new": "Nuevo horario",
|
||||||
"schedule_edit": "Editar horario",
|
"schedule_edit": "Editar horario",
|
||||||
"schedule_save": "Guardar horario",
|
"schedule_save": "Guardar horario",
|
||||||
|
@ -139,7 +139,6 @@
|
|||||||
"enforced_save_search": "جستجوی اَمن اجبار شده",
|
"enforced_save_search": "جستجوی اَمن اجبار شده",
|
||||||
"number_of_dns_query_to_safe_search": "تعداد درخواست های DNS برای موتور جستجو که جستجوی اَمن اجبار شده",
|
"number_of_dns_query_to_safe_search": "تعداد درخواست های DNS برای موتور جستجو که جستجوی اَمن اجبار شده",
|
||||||
"average_processing_time": "میانگین زمان پردازش",
|
"average_processing_time": "میانگین زمان پردازش",
|
||||||
"processing_time": "زمان پردازش",
|
|
||||||
"average_processing_time_hint": "زمان میانگین بر هزارم ثانیه در پردازش درخواست DNS",
|
"average_processing_time_hint": "زمان میانگین بر هزارم ثانیه در پردازش درخواست DNS",
|
||||||
"block_domain_use_filters_and_hosts": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان",
|
"block_domain_use_filters_and_hosts": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان",
|
||||||
"filters_block_toggle_hint": "میتوانید دستورات مسدودسازی را در تنظیمات <a>فیلترها</a> راه اندازی کنید.",
|
"filters_block_toggle_hint": "میتوانید دستورات مسدودسازی را در تنظیمات <a>فیلترها</a> راه اندازی کنید.",
|
||||||
|
@ -143,7 +143,7 @@
|
|||||||
"enforced_save_search": "Turvallinen haku pakotettiin",
|
"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",
|
"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": "Keskimääräinen käsittelyaika",
|
||||||
"processing_time": "Käsittelyaika",
|
"response_time": "Vasteaika",
|
||||||
"average_processing_time_hint": "Keskimääräinen DNS-pyynnön käsittelyyn kulutettu aika millisekunteina",
|
"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",
|
"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>.",
|
"filters_block_toggle_hint": "Voit määrittää estosääntöjä <a>suodatinasetuksissa</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Paramètres du client",
|
"client_settings": "Paramètres du client",
|
||||||
"example_upstream_reserved": "un amont <0>pour des domaines spécifiques</0> ;",
|
"example_upstream_reserved": "un amont <0>pour des domaines spécifiques</0> ;",
|
||||||
|
"example_multiple_upstreams_reserved": "plusieurs amonts <0>pour des domaines spécifiques</0> ;",
|
||||||
"example_upstream_comment": " un commentaire.",
|
"example_upstream_comment": " un commentaire.",
|
||||||
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
|
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
|
||||||
"parallel_requests": "Requêtes en parallèle",
|
"parallel_requests": "Requêtes en parallèle",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Recherche sécurisée forcée",
|
"enforced_save_search": "Recherche sécurisée forcée",
|
||||||
"number_of_dns_query_to_safe_search": "Le nombre de requêtes DNS faites avec la Recherche securisée",
|
"number_of_dns_query_to_safe_search": "Le nombre de requêtes DNS faites avec la Recherche securisée",
|
||||||
"average_processing_time": "Temps moyen de traitement",
|
"average_processing_time": "Temps moyen de traitement",
|
||||||
"processing_time": "Délai de traitement",
|
"average_upstream_response_time": "Temps de réponse moyen en amont",
|
||||||
|
"response_time": "Temps de réponse",
|
||||||
"average_processing_time_hint": "Temps moyen (en millisecondes) de traitement d'une requête DNS",
|
"average_processing_time_hint": "Temps moyen (en millisecondes) de traitement d'une requête DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Bloquez les domaines à l'aide des filtres et fichiers hosts",
|
"block_domain_use_filters_and_hosts": "Bloquez les domaines à l'aide des filtres et fichiers hosts",
|
||||||
"filters_block_toggle_hint": "Vous pouvez configurer les règles de filtrage dans les paramètres des <a>Filtres</a>.",
|
"filters_block_toggle_hint": "Vous pouvez configurer les règles de filtrage dans les paramètres des <a>Filtres</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Postavke klijenta",
|
"client_settings": "Postavke klijenta",
|
||||||
"example_upstream_reserved": "upstream <0>za određene domene</0>;",
|
"example_upstream_reserved": "upstream <0>za određene domene</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "višestruke upstream poslužitelje <0>za određene domene</0>;",
|
||||||
"example_upstream_comment": "komentar.",
|
"example_upstream_comment": "komentar.",
|
||||||
"upstream_parallel": "Koristi paralelne upite kako bi ubrzali rješavanje istovremenim ispitavanjem svih upstream poslužitelja.",
|
"upstream_parallel": "Koristi paralelne upite kako bi ubrzali rješavanje istovremenim ispitavanjem svih upstream poslužitelja.",
|
||||||
"parallel_requests": "Paralelni zahtjevi",
|
"parallel_requests": "Paralelni zahtjevi",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Omogućeno sigurno pretraživanje",
|
"enforced_save_search": "Omogućeno sigurno pretraživanje",
|
||||||
"number_of_dns_query_to_safe_search": "Broj DNS zahtjeva prema pretraživačima za koje je omogućeno Sigurno pretraživanje",
|
"number_of_dns_query_to_safe_search": "Broj DNS zahtjeva prema pretraživačima za koje je omogućeno Sigurno pretraživanje",
|
||||||
"average_processing_time": "Prosječno vrijeme obrade",
|
"average_processing_time": "Prosječno vrijeme obrade",
|
||||||
"processing_time": "Vrijeme obrade",
|
"average_upstream_response_time": "Prosječno vrijeme odziva upstream poslužitelja",
|
||||||
|
"response_time": "Vrijeme odziva",
|
||||||
"average_processing_time_hint": "Prosječno vrijeme u milisekundama za obradu DNS zahtjeva",
|
"average_processing_time_hint": "Prosječno vrijeme u milisekundama za obradu DNS zahtjeva",
|
||||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
|
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
|
||||||
"filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama <a>filtara</a>.",
|
"filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama <a>filtara</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Kliens beállítások",
|
"client_settings": "Kliens beállítások",
|
||||||
"example_upstream_reserved": "Megadhat egy DNS kiszolgálót <0>egy adott domainhez vagy domainekhez</0>",
|
"example_upstream_reserved": "Megadhat egy DNS kiszolgálót <0>egy adott domainhez vagy domainekhez</0>",
|
||||||
|
"example_multiple_upstreams_reserved": "több upstream szerver <0>adott domainekhez</0>;",
|
||||||
"example_upstream_comment": "egy megjegyzés.",
|
"example_upstream_comment": "egy megjegyzés.",
|
||||||
"upstream_parallel": "Használjon párhuzamos lekéréseket a domainek feloldásának felgyorsításához az összes upstream kiszolgálóra való egyidejű lekérdezéssel.",
|
"upstream_parallel": "Használjon párhuzamos lekéréseket a domainek feloldásának felgyorsításához az összes upstream kiszolgálóra való egyidejű lekérdezéssel.",
|
||||||
"parallel_requests": "Párhuzamos lekérések",
|
"parallel_requests": "Párhuzamos lekérések",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Kényszerített biztonságos keresés",
|
"enforced_save_search": "Kényszerített biztonságos keresés",
|
||||||
"number_of_dns_query_to_safe_search": "A biztonságos keresésre kényszerített DNS lekérdezések száma",
|
"number_of_dns_query_to_safe_search": "A biztonságos keresésre kényszerített DNS lekérdezések száma",
|
||||||
"average_processing_time": "Átlagos feldolgozási idő",
|
"average_processing_time": "Átlagos feldolgozási idő",
|
||||||
"processing_time": "Feldolgozási idő",
|
"average_upstream_response_time": "Átlagos upstream válaszidő",
|
||||||
|
"response_time": "Válaszidő",
|
||||||
"average_processing_time_hint": "A DNS lekérdezések feldolgozásához szükséges átlagos idő milliszekundumban",
|
"average_processing_time_hint": "A DNS lekérdezések feldolgozásához szükséges átlagos idő milliszekundumban",
|
||||||
"block_domain_use_filters_and_hosts": "Domainek blokkolása szűrők és hosztfájlok használatával",
|
"block_domain_use_filters_and_hosts": "Domainek blokkolása szűrők és hosztfájlok használatával",
|
||||||
"filters_block_toggle_hint": "A <a> szűrőbeállításoknál</a> megadhatja a blokkolási szabályokat.",
|
"filters_block_toggle_hint": "A <a> szűrőbeállításoknál</a> megadhatja a blokkolási szabályokat.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Pengaturan klien",
|
"client_settings": "Pengaturan klien",
|
||||||
"example_upstream_reserved": "upstream <0>untuk domain spesifik</0>;",
|
"example_upstream_reserved": "upstream <0>untuk domain spesifik</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "beberapa server upstream <0>untuk domain spesifik</0>;",
|
||||||
"example_upstream_comment": "komentar.",
|
"example_upstream_comment": "komentar.",
|
||||||
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
||||||
"parallel_requests": "Permintaan paralel",
|
"parallel_requests": "Permintaan paralel",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Paksa pencarian aman",
|
"enforced_save_search": "Paksa pencarian aman",
|
||||||
"number_of_dns_query_to_safe_search": "Jumlah perminataan DNS ke mesin pencari yang dipaksa Pencarian Aman",
|
"number_of_dns_query_to_safe_search": "Jumlah perminataan DNS ke mesin pencari yang dipaksa Pencarian Aman",
|
||||||
"average_processing_time": "Rata-rata waktu pemrosesan",
|
"average_processing_time": "Rata-rata waktu pemrosesan",
|
||||||
"processing_time": "Waktu pemrosesan",
|
"average_upstream_response_time": "Waktu respons server upstream rata-rata",
|
||||||
|
"response_time": "Waktu respons",
|
||||||
"average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS",
|
"average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan file hosts",
|
"block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan file hosts",
|
||||||
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran di pengaturan <a>Penyaringan</a>.",
|
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran di pengaturan <a>Penyaringan</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Impostazioni client",
|
"client_settings": "Impostazioni client",
|
||||||
"example_upstream_reserved": "un upstream <0>per specifici domini</0>;",
|
"example_upstream_reserved": "un upstream <0>per specifici domini</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "upstream multipli <0>per domini specifici</0>;",
|
||||||
"example_upstream_comment": "un commento.",
|
"example_upstream_comment": "un commento.",
|
||||||
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
|
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
|
||||||
"parallel_requests": "Richieste parallele",
|
"parallel_requests": "Richieste parallele",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Ricerca sicura forzata",
|
"enforced_save_search": "Ricerca sicura forzata",
|
||||||
"number_of_dns_query_to_safe_search": "Numero di richieste DNS dai motori di ricerca per i quali la Ricerca Sicura è stata forzata",
|
"number_of_dns_query_to_safe_search": "Numero di richieste DNS dai motori di ricerca per i quali la Ricerca Sicura è stata forzata",
|
||||||
"average_processing_time": "Tempo di elaborazione medio",
|
"average_processing_time": "Tempo di elaborazione medio",
|
||||||
"processing_time": "Tempo di elaborazione",
|
"average_upstream_response_time": "Tempo medio di risposta upstream",
|
||||||
|
"response_time": "Tempo di risposta",
|
||||||
"average_processing_time_hint": "Tempo medio in millisecondi per elaborare una richiesta DNS",
|
"average_processing_time_hint": "Tempo medio in millisecondi per elaborare una richiesta DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Blocca domini utilizzando filtri e file hosts",
|
"block_domain_use_filters_and_hosts": "Blocca domini utilizzando filtri e file hosts",
|
||||||
"filters_block_toggle_hint": "Puoi impostare le regole di blocco nelle impostazioni dei <a>Filtri</a>.",
|
"filters_block_toggle_hint": "Puoi impostare le regole di blocco nelle impostazioni dei <a>Filtri</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "クライアント設定",
|
"client_settings": "クライアント設定",
|
||||||
"example_upstream_reserved": "<0>特定のドメイン</0>に対してDNSアップストリームを指定できます。",
|
"example_upstream_reserved": "<0>特定のドメイン</0>に対してDNSアップストリームを指定できます。",
|
||||||
|
"example_multiple_upstreams_reserved": "<0>特定ドメイン</0>のための複数のアップストリームサーバー;",
|
||||||
"example_upstream_comment": "コメントを追加できます。",
|
"example_upstream_comment": "コメントを追加できます。",
|
||||||
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
|
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
|
||||||
"parallel_requests": "並列リクエスト",
|
"parallel_requests": "並列リクエスト",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "強制されたセーフサーチ",
|
"enforced_save_search": "強制されたセーフサーチ",
|
||||||
"number_of_dns_query_to_safe_search": "セーフサーチが強制適用された検索エンジンへのDNSリクエストの数",
|
"number_of_dns_query_to_safe_search": "セーフサーチが強制適用された検索エンジンへのDNSリクエストの数",
|
||||||
"average_processing_time": "平均処理時間",
|
"average_processing_time": "平均処理時間",
|
||||||
"processing_time": "処理時間",
|
"average_upstream_response_time": "アップストリームの平均応答時間",
|
||||||
|
"response_time": "応答時間",
|
||||||
"average_processing_time_hint": "DNSリクエストの処理にかかる平均時間(ミリ秒単位)",
|
"average_processing_time_hint": "DNSリクエストの処理にかかる平均時間(ミリ秒単位)",
|
||||||
"block_domain_use_filters_and_hosts": "フィルタとhostsファイルを使用してドメインをブロックする",
|
"block_domain_use_filters_and_hosts": "フィルタとhostsファイルを使用してドメインをブロックする",
|
||||||
"filters_block_toggle_hint": "<a>フィルタ</a>の設定でブロックするルールを設定することができます。",
|
"filters_block_toggle_hint": "<a>フィルタ</a>の設定でブロックするルールを設定することができます。",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "클라이언트 설정",
|
"client_settings": "클라이언트 설정",
|
||||||
"example_upstream_reserved": "<0>특정 도메인에 대한</0> 업스트림;",
|
"example_upstream_reserved": "<0>특정 도메인에 대한</0> 업스트림;",
|
||||||
|
"example_multiple_upstreams_reserved": "<0>특정 도메인</0>에 대한 여러 업스트림",
|
||||||
"example_upstream_comment": "댓글.",
|
"example_upstream_comment": "댓글.",
|
||||||
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
|
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
|
||||||
"parallel_requests": "병렬 처리 요청",
|
"parallel_requests": "병렬 처리 요청",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "세이프서치 강제",
|
"enforced_save_search": "세이프서치 강제",
|
||||||
"number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수",
|
"number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수",
|
||||||
"average_processing_time": "평균처리 시간",
|
"average_processing_time": "평균처리 시간",
|
||||||
"processing_time": "처리 시간",
|
"average_upstream_response_time": "평균 업스트림 응답 시간",
|
||||||
|
"response_time": "응답 시간",
|
||||||
"average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)",
|
"average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)",
|
||||||
"block_domain_use_filters_and_hosts": "필터 및 호스트 파일을 사용하여 도메인 차단",
|
"block_domain_use_filters_and_hosts": "필터 및 호스트 파일을 사용하여 도메인 차단",
|
||||||
"filters_block_toggle_hint": "차단규칙<a>필터</a>을 설정할 수 있습니다.",
|
"filters_block_toggle_hint": "차단규칙<a>필터</a>을 설정할 수 있습니다.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Cliëntinstellingen",
|
"client_settings": "Cliëntinstellingen",
|
||||||
"example_upstream_reserved": "een upstream <0>voor specifieke domeinen</0>;",
|
"example_upstream_reserved": "een upstream <0>voor specifieke domeinen</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "meerdere upstreams <0>voor specifieke domeinen</0>;",
|
||||||
"example_upstream_comment": "een commentaar.",
|
"example_upstream_comment": "een commentaar.",
|
||||||
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
|
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
|
||||||
"parallel_requests": "Parallelle verzoeken",
|
"parallel_requests": "Parallelle verzoeken",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Geforceerd veilig zoeken",
|
"enforced_save_search": "Geforceerd veilig zoeken",
|
||||||
"number_of_dns_query_to_safe_search": "Aantal DNS aanvragen in zoekmachines dmv geforceerd veilig zoeken",
|
"number_of_dns_query_to_safe_search": "Aantal DNS aanvragen in zoekmachines dmv geforceerd veilig zoeken",
|
||||||
"average_processing_time": "Gemiddelde procestijd",
|
"average_processing_time": "Gemiddelde procestijd",
|
||||||
"processing_time": "Verwerkingstijd",
|
"average_upstream_response_time": "Gemiddelde upstream responstijd",
|
||||||
|
"response_time": "Responsetijd",
|
||||||
"average_processing_time_hint": "Gemiddelde verwerkingstijd in milliseconden van een DNS aanvraag",
|
"average_processing_time_hint": "Gemiddelde verwerkingstijd in milliseconden van een DNS aanvraag",
|
||||||
"block_domain_use_filters_and_hosts": "Domeinen blokkeren d.m.v. filters en host-bestanden",
|
"block_domain_use_filters_and_hosts": "Domeinen blokkeren d.m.v. filters en host-bestanden",
|
||||||
"filters_block_toggle_hint": "Je kan blokkeringsregels toevoegen in de <a>Filters</a> instellingen.",
|
"filters_block_toggle_hint": "Je kan blokkeringsregels toevoegen in de <a>Filters</a> instellingen.",
|
||||||
|
@ -128,7 +128,6 @@
|
|||||||
"enforced_save_search": "Påtvungede barnevennlige søk",
|
"enforced_save_search": "Påtvungede barnevennlige søk",
|
||||||
"number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget",
|
"number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget",
|
||||||
"average_processing_time": "Gjennomsnittlig behandlingstid",
|
"average_processing_time": "Gjennomsnittlig behandlingstid",
|
||||||
"processing_time": "Behandlingstid",
|
|
||||||
"average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder",
|
"average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder",
|
||||||
"block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener",
|
"block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener",
|
||||||
"filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i <a>Filtre</a>-innstillingene.",
|
"filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i <a>Filtre</a>-innstillingene.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Ustawienia klienta",
|
"client_settings": "Ustawienia klienta",
|
||||||
"example_upstream_reserved": "upstream <0>dla określonych domen</0>;",
|
"example_upstream_reserved": "upstream <0>dla określonych domen</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "wiele serwerów nadrzędnych <0>dla konkretnej domeny</0>;",
|
||||||
"example_upstream_comment": "komentarz.",
|
"example_upstream_comment": "komentarz.",
|
||||||
"upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.",
|
"upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.",
|
||||||
"parallel_requests": "Równoległe żądania",
|
"parallel_requests": "Równoległe żądania",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Wymuszone bezpieczne wyszukiwanie",
|
"enforced_save_search": "Wymuszone bezpieczne wyszukiwanie",
|
||||||
"number_of_dns_query_to_safe_search": "Liczba żądań DNS kierowanych do wyszukiwarek, dla których wymuszono Bezpieczne wyszukiwanie",
|
"number_of_dns_query_to_safe_search": "Liczba żądań DNS kierowanych do wyszukiwarek, dla których wymuszono Bezpieczne wyszukiwanie",
|
||||||
"average_processing_time": "Średni czas przetwarzania",
|
"average_processing_time": "Średni czas przetwarzania",
|
||||||
"processing_time": "Czas przetwarzania",
|
"average_upstream_response_time": "Średni czas odpowiedzi serwera nadrzędnego",
|
||||||
|
"response_time": "Czas odpowiedzi",
|
||||||
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
|
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
|
||||||
"block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host",
|
"block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host",
|
||||||
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a>Filtry</a>.",
|
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a>Filtry</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Configurações do cliente",
|
"client_settings": "Configurações do cliente",
|
||||||
"example_upstream_reserved": "um DNS primário <0>para o domínios especificos</0>;",
|
"example_upstream_reserved": "um DNS primário <0>para o domínios especificos</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "múltiplos upstreams <0>para domínios específicos</0>;",
|
||||||
"example_upstream_comment": "um comentário.",
|
"example_upstream_comment": "um comentário.",
|
||||||
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
|
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
|
||||||
"parallel_requests": "Solicitações paralelas",
|
"parallel_requests": "Solicitações paralelas",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Forçar pesquisa segura",
|
"enforced_save_search": "Forçar pesquisa segura",
|
||||||
"number_of_dns_query_to_safe_search": "O número de solicitações de DNS para mecanismos de pesquisa para os quais a pesquisa segura foi aplicada",
|
"number_of_dns_query_to_safe_search": "O número de solicitações de DNS para mecanismos de pesquisa para os quais a pesquisa segura foi aplicada",
|
||||||
"average_processing_time": "Tempo médio de processamento",
|
"average_processing_time": "Tempo médio de processamento",
|
||||||
"processing_time": "Tempo de processamento",
|
"average_upstream_response_time": "Tempo médio de resposta upstream",
|
||||||
|
"response_time": "Tempo de resposta",
|
||||||
"average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS",
|
"average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Bloquear domínios usando arquivos de filtros e hosts",
|
"block_domain_use_filters_and_hosts": "Bloquear domínios usando arquivos de filtros e hosts",
|
||||||
"filters_block_toggle_hint": "Você pode configurar as regras de bloqueio nas configurações de <a>Filtros</a>.",
|
"filters_block_toggle_hint": "Você pode configurar as regras de bloqueio nas configurações de <a>Filtros</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Definições do cliente",
|
"client_settings": "Definições do cliente",
|
||||||
"example_upstream_reserved": "Podes especificar o DNS primário <0>para domínio(s) especifico(s)</0>",
|
"example_upstream_reserved": "Podes especificar o DNS primário <0>para domínio(s) especifico(s)</0>",
|
||||||
|
"example_multiple_upstreams_reserved": "múltiplos upstreams <0>para domínios específicos</0>;",
|
||||||
"example_upstream_comment": "um comentário.",
|
"example_upstream_comment": "um comentário.",
|
||||||
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
|
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
|
||||||
"parallel_requests": "Solicitações paralelas",
|
"parallel_requests": "Solicitações paralelas",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Forçar pesquisa segura",
|
"enforced_save_search": "Forçar pesquisa segura",
|
||||||
"number_of_dns_query_to_safe_search": "O número de solicitações de DNS para motores de busca para os quais a pesquisa segura foi aplicada",
|
"number_of_dns_query_to_safe_search": "O número de solicitações de DNS para motores de busca para os quais a pesquisa segura foi aplicada",
|
||||||
"average_processing_time": "Tempo médio de processamento",
|
"average_processing_time": "Tempo médio de processamento",
|
||||||
"processing_time": "Tempo de processamento",
|
"average_upstream_response_time": "Tempo médio de resposta upstream",
|
||||||
|
"response_time": "Tempo de resposta",
|
||||||
"average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS",
|
"average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Bloquear domínios usando ficheiros de filtros e hosts",
|
"block_domain_use_filters_and_hosts": "Bloquear domínios usando ficheiros de filtros e hosts",
|
||||||
"filters_block_toggle_hint": "Pode configurar as regras de bloqueio nas configurações de <a>Filtros</a>.",
|
"filters_block_toggle_hint": "Pode configurar as regras de bloqueio nas configurações de <a>Filtros</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Setări client",
|
"client_settings": "Setări client",
|
||||||
"example_upstream_reserved": "un flux în amonte <0>pentru domenii specifice</0>;",
|
"example_upstream_reserved": "un flux în amonte <0>pentru domenii specifice</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "mai mulți servere în amonte <0>pentru domenii specifice</0>;",
|
||||||
"example_upstream_comment": "un comentariu.",
|
"example_upstream_comment": "un comentariu.",
|
||||||
"upstream_parallel": "Folosiți interogări paralele pentru a accelera rezolvarea, interogând simultan toate serverele în amonte.",
|
"upstream_parallel": "Folosiți interogări paralele pentru a accelera rezolvarea, interogând simultan toate serverele în amonte.",
|
||||||
"parallel_requests": "Solicitări paralele",
|
"parallel_requests": "Solicitări paralele",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Căutare protejată întărită",
|
"enforced_save_search": "Căutare protejată întărită",
|
||||||
"number_of_dns_query_to_safe_search": "Numărul de interogări DNS pe motoarele de căutare pentru care a fost impusă Căutarea Sigură",
|
"number_of_dns_query_to_safe_search": "Numărul de interogări DNS pe motoarele de căutare pentru care a fost impusă Căutarea Sigură",
|
||||||
"average_processing_time": "Timpul mediu de procesare",
|
"average_processing_time": "Timpul mediu de procesare",
|
||||||
"processing_time": "Timp de procesare",
|
"average_upstream_response_time": "Timpul mediu de răspuns al serverului în amonte",
|
||||||
|
"response_time": "Timp de răspuns",
|
||||||
"average_processing_time_hint": "Timp mediu în milisecunde la procesarea unei cereri DNS",
|
"average_processing_time_hint": "Timp mediu în milisecunde la procesarea unei cereri DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Blocați domenii folosind filtre și fișiere hosts",
|
"block_domain_use_filters_and_hosts": "Blocați domenii folosind filtre și fișiere hosts",
|
||||||
"filters_block_toggle_hint": "Puteți configura regulile de blocare în setările <a>Filtre</a>.",
|
"filters_block_toggle_hint": "Puteți configura regulile de blocare în setările <a>Filtre</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Настройки клиентов",
|
"client_settings": "Настройки клиентов",
|
||||||
"example_upstream_reserved": "DNS-сервер <0>для конкретных доменов</0>;",
|
"example_upstream_reserved": "DNS-сервер <0>для конкретных доменов</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "несколько DNS-серверов <0>для конкретных доменов</0>;",
|
||||||
"example_upstream_comment": "комментарий.",
|
"example_upstream_comment": "комментарий.",
|
||||||
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
|
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
|
||||||
"parallel_requests": "Параллельные запросы",
|
"parallel_requests": "Параллельные запросы",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Применён безопасный поиск",
|
"enforced_save_search": "Применён безопасный поиск",
|
||||||
"number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск",
|
"number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск",
|
||||||
"average_processing_time": "Среднее время обработки запроса",
|
"average_processing_time": "Среднее время обработки запроса",
|
||||||
"processing_time": "Время обработки",
|
"average_upstream_response_time": "Среднее время ответа upstream-сервера",
|
||||||
|
"response_time": "Время ответа",
|
||||||
"average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах",
|
"average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах",
|
||||||
"block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов hosts",
|
"block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов hosts",
|
||||||
"filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.",
|
"filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.",
|
||||||
|
@ -122,7 +122,6 @@
|
|||||||
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ",
|
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ",
|
||||||
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
||||||
"processing_time": "සැකසුම් කාලය",
|
|
||||||
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
||||||
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
|
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
|
||||||
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
||||||
@ -647,6 +646,20 @@
|
|||||||
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්යාලේඛන",
|
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්යාලේඛන",
|
||||||
"ignore_query_log": "විමසුම් සටහනට මෙම අනුග්රාහකය යොදන්න එපා",
|
"ignore_query_log": "විමසුම් සටහනට මෙම අනුග්රාහකය යොදන්න එපා",
|
||||||
"ignore_statistics": "සංඛ්යාලේඛනයට මෙම අනුග්රාහකය යොදන්න එපා",
|
"ignore_statistics": "සංඛ්යාලේඛනයට මෙම අනුග්රාහකය යොදන්න එපා",
|
||||||
|
"schedule_invalid_select": "ආරම්භක වේලාව අවසන් වේලාවට කලින් විය යුතුය",
|
||||||
|
"schedule_select_days": "දවස් තෝරන්න",
|
||||||
|
"schedule_timezone": "වේලා කලාපයක් තෝරන්න",
|
||||||
|
"schedule_current_timezone": "වත්මන් වේලා කලාපය: {{value}}",
|
||||||
|
"schedule_time_all_day": "දවස පුරාම",
|
||||||
|
"schedule_from": "සිට",
|
||||||
|
"schedule_to": "දක්වා",
|
||||||
|
"sunday": "ඉරිදා",
|
||||||
|
"monday": "සඳුදා",
|
||||||
|
"tuesday": "අඟහරුවාදා",
|
||||||
|
"wednesday": "බදාදා",
|
||||||
|
"thursday": "බ්රහස්පතින්දා",
|
||||||
|
"friday": "සිකුරාදා",
|
||||||
|
"saturday": "සෙනසුරාදා",
|
||||||
"sunday_short": "ඉරිදා",
|
"sunday_short": "ඉරිදා",
|
||||||
"monday_short": "සඳුදා",
|
"monday_short": "සඳුදා",
|
||||||
"tuesday_short": "අඟහ",
|
"tuesday_short": "අඟහ",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Nastavenie klienta",
|
"client_settings": "Nastavenie klienta",
|
||||||
"example_upstream_reserved": "upstream <0>pre konkrétne domény</0>;",
|
"example_upstream_reserved": "upstream <0>pre konkrétne domény</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "viaceré upstreamy pre <0>konkrétne domény</0>;",
|
||||||
"example_upstream_comment": "komentár.",
|
"example_upstream_comment": "komentár.",
|
||||||
"upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
|
"upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
|
||||||
"parallel_requests": "Paralelné dopyty",
|
"parallel_requests": "Paralelné dopyty",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Vynútené bezpečné vyhľadávanie",
|
"enforced_save_search": "Vynútené bezpečné vyhľadávanie",
|
||||||
"number_of_dns_query_to_safe_search": "Počet DNS dopytov na vyhľadávače, pri ktorých bolo vynútené bezpečné vyhľadávanie",
|
"number_of_dns_query_to_safe_search": "Počet DNS dopytov na vyhľadávače, pri ktorých bolo vynútené bezpečné vyhľadávanie",
|
||||||
"average_processing_time": "Priemerný čas spracovania",
|
"average_processing_time": "Priemerný čas spracovania",
|
||||||
"processing_time": "Doba spracovania",
|
"average_upstream_response_time": "Priemerný čas odozvy upstreamu",
|
||||||
|
"response_time": "Čas odozvy",
|
||||||
"average_processing_time_hint": "Priemerný čas spracovania DNS dopytu v milisekundách",
|
"average_processing_time_hint": "Priemerný čas spracovania DNS dopytu v milisekundách",
|
||||||
"block_domain_use_filters_and_hosts": "Blokovať domény pomocou filtrov a zoznamov adries",
|
"block_domain_use_filters_and_hosts": "Blokovať domény pomocou filtrov a zoznamov adries",
|
||||||
"filters_block_toggle_hint": "Pravidlá blokovania môžete nastaviť v nastaveniach <a>Filtre</a>.",
|
"filters_block_toggle_hint": "Pravidlá blokovania môžete nastaviť v nastaveniach <a>Filtre</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Nastavitve odjemalca",
|
"client_settings": "Nastavitve odjemalca",
|
||||||
"example_upstream_reserved": "gorvodni <0>za določene domene</0>;",
|
"example_upstream_reserved": "gorvodni <0>za določene domene</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "več gorvodnih <0>za določene domene</0>;",
|
||||||
"example_upstream_comment": "komentar.",
|
"example_upstream_comment": "komentar.",
|
||||||
"upstream_parallel": "Uporabite vzporedne zahteve za pospešitev reševanja s hkratnim poizvedovanjem vseh gorvodnih strežnikov.",
|
"upstream_parallel": "Uporabite vzporedne zahteve za pospešitev reševanja s hkratnim poizvedovanjem vseh gorvodnih strežnikov.",
|
||||||
"parallel_requests": "Vzporedne zahteve",
|
"parallel_requests": "Vzporedne zahteve",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Prisilno varno iskanje",
|
"enforced_save_search": "Prisilno varno iskanje",
|
||||||
"number_of_dns_query_to_safe_search": "Število zahtev DNS za iskalnike, za katere je bilo uveljavljeno varno iskanje",
|
"number_of_dns_query_to_safe_search": "Število zahtev DNS za iskalnike, za katere je bilo uveljavljeno varno iskanje",
|
||||||
"average_processing_time": "Povprečni čas obdelave",
|
"average_processing_time": "Povprečni čas obdelave",
|
||||||
"processing_time": "Čas obdelave",
|
"average_upstream_response_time": "Povprečni gorvodni odzivni čas",
|
||||||
|
"response_time": "Odzivni čas",
|
||||||
"average_processing_time_hint": "Povprečni čas v milisekundah pri obdelavi zahteve DNS",
|
"average_processing_time_hint": "Povprečni čas v milisekundah pri obdelavi zahteve DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Onemogoči domene s filtri in gostiteljskimi datotekami",
|
"block_domain_use_filters_and_hosts": "Onemogoči domene s filtri in gostiteljskimi datotekami",
|
||||||
"filters_block_toggle_hint": "Pravila zaviranja lahko nastavite v nastavitvah <a>Filtri</a>.",
|
"filters_block_toggle_hint": "Pravila zaviranja lahko nastavite v nastavitvah <a>Filtri</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Postavke klijenta",
|
"client_settings": "Postavke klijenta",
|
||||||
"example_upstream_reserved": "upstream <0>za određene domene</0>;",
|
"example_upstream_reserved": "upstream <0>za određene domene</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "nekoliko DNS servera <0>za određene domene</0>;",
|
||||||
"example_upstream_comment": "komentar.",
|
"example_upstream_comment": "komentar.",
|
||||||
"upstream_parallel": "Koristite paralelne upite da biste ubrzali rešavanje tako što ćete istovremeno ispitati sve uzvodne servere.",
|
"upstream_parallel": "Koristite paralelne upite da biste ubrzali rešavanje tako što ćete istovremeno ispitati sve uzvodne servere.",
|
||||||
"parallel_requests": "Paralelni zahtevi",
|
"parallel_requests": "Paralelni zahtevi",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Nametni sigurno pretraživanje",
|
"enforced_save_search": "Nametni sigurno pretraživanje",
|
||||||
"number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje",
|
"number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje",
|
||||||
"average_processing_time": "Prosečno vreme obrade",
|
"average_processing_time": "Prosečno vreme obrade",
|
||||||
"processing_time": "Vreme obrade",
|
"average_upstream_response_time": "Prosečno vreme odziva upstream-servera",
|
||||||
|
"response_time": "Vreme odziva",
|
||||||
"average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva",
|
"average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva",
|
||||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke",
|
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke",
|
||||||
"filters_block_toggle_hint": "Možete postaviti pravila blokiranja u <a>Filters</a> postavkama.",
|
"filters_block_toggle_hint": "Možete postaviti pravila blokiranja u <a>Filters</a> postavkama.",
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Klientinställningar",
|
"client_settings": "Klientinställningar",
|
||||||
"example_upstream_reserved": "uppström <0>för en specifik domän</0>;",
|
"example_upstream_reserved": "uppström <0>för en specifik domän</0>;",
|
||||||
"example_upstream_comment": "du kan ange en kommentar.",
|
"example_multiple_upstreams_reserved": "flera uppströmsservrar <0>för specifika domäner</0>;",
|
||||||
|
"example_upstream_comment": "en kommentar.",
|
||||||
"upstream_parallel": "Använd parallella förfrågningar för att snabba upp dessa genom att fråga alla uppströmsservrar samtidigt.",
|
"upstream_parallel": "Använd parallella förfrågningar för att snabba upp dessa genom att fråga alla uppströmsservrar samtidigt.",
|
||||||
"parallel_requests": "Parallella förfrågningar",
|
"parallel_requests": "Parallella förfrågningar",
|
||||||
"load_balancing": "Lastbalansering",
|
"load_balancing": "Lastbalansering",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Aktivering av Säker surf",
|
"enforced_save_search": "Aktivering av Säker surf",
|
||||||
"number_of_dns_query_to_safe_search": "Antalet DNS-förfrågningar mot sökmotorer där Säker surf tvingats",
|
"number_of_dns_query_to_safe_search": "Antalet DNS-förfrågningar mot sökmotorer där Säker surf tvingats",
|
||||||
"average_processing_time": "Genomsnittlig processtid",
|
"average_processing_time": "Genomsnittlig processtid",
|
||||||
"processing_time": "Bearbetningstid",
|
"average_upstream_response_time": "Genomsnittlig svarstid uppströmsserver",
|
||||||
|
"response_time": "Svarstid",
|
||||||
"average_processing_time_hint": "Genomsnittlig processtid i millisekunder för DNS-förfrågning",
|
"average_processing_time_hint": "Genomsnittlig processtid i millisekunder för DNS-förfrågning",
|
||||||
"block_domain_use_filters_and_hosts": "Blockera domäner med filter- och värdfiler",
|
"block_domain_use_filters_and_hosts": "Blockera domäner med filter- och värdfiler",
|
||||||
"filters_block_toggle_hint": "Du kan ställa in egna blockerings regler i <a>Filterinställningar</a>.",
|
"filters_block_toggle_hint": "Du kan ställa in egna blockerings regler i <a>Filterinställningar</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "İstemci ayarları",
|
"client_settings": "İstemci ayarları",
|
||||||
"example_upstream_reserved": "<0>belirli alan adları</0> için bir üst sunucusu;",
|
"example_upstream_reserved": "<0>belirli alan adları</0> için bir üst sunucusu;",
|
||||||
|
"example_multiple_upstreams_reserved": "<0>belirli alanlar için</0> birden fazla üst kaynaklar;",
|
||||||
"example_upstream_comment": "bir yorum.",
|
"example_upstream_comment": "bir yorum.",
|
||||||
"upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
|
"upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
|
||||||
"parallel_requests": "Paralel istekler",
|
"parallel_requests": "Paralel istekler",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Uygulanan güvenli arama",
|
"enforced_save_search": "Uygulanan güvenli arama",
|
||||||
"number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
|
"number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
|
||||||
"average_processing_time": "Ortalama işlem süresi",
|
"average_processing_time": "Ortalama işlem süresi",
|
||||||
"processing_time": "İşlem süresi",
|
"average_upstream_response_time": "Ortalama üst kaynak yanıt süresi",
|
||||||
|
"response_time": "Yanıt süresi",
|
||||||
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
|
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
|
||||||
"block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle",
|
"block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle",
|
||||||
"filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
|
"filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
|
||||||
@ -180,7 +182,7 @@
|
|||||||
"enabled_save_search_toast": "Güvenli Arama etkinleştirildi",
|
"enabled_save_search_toast": "Güvenli Arama etkinleştirildi",
|
||||||
"updated_save_search_toast": "Güvenli Arama ayarları güncellendi",
|
"updated_save_search_toast": "Güvenli Arama ayarları güncellendi",
|
||||||
"enabled_table_header": "Etkin",
|
"enabled_table_header": "Etkin",
|
||||||
"name_table_header": "Ad",
|
"name_table_header": "Adı",
|
||||||
"list_url_table_header": "Liste URL'si",
|
"list_url_table_header": "Liste URL'si",
|
||||||
"rules_count_table_header": "Kural sayısı",
|
"rules_count_table_header": "Kural sayısı",
|
||||||
"last_time_updated_table_header": "Son güncelleme zamanı",
|
"last_time_updated_table_header": "Son güncelleme zamanı",
|
||||||
@ -435,7 +437,7 @@
|
|||||||
"settings_global": "Genel",
|
"settings_global": "Genel",
|
||||||
"settings_custom": "Özel",
|
"settings_custom": "Özel",
|
||||||
"table_client": "İstemci",
|
"table_client": "İstemci",
|
||||||
"table_name": "Ad",
|
"table_name": "AdAdı",
|
||||||
"save_btn": "Kaydet",
|
"save_btn": "Kaydet",
|
||||||
"client_add": "İstemci Ekle",
|
"client_add": "İstemci Ekle",
|
||||||
"client_new": "Yeni İstemci",
|
"client_new": "Yeni İstemci",
|
||||||
@ -449,7 +451,7 @@
|
|||||||
"form_enter_id": "Tanımlayıcı girin",
|
"form_enter_id": "Tanımlayıcı girin",
|
||||||
"form_add_id": "Tanımlayıcı ekle",
|
"form_add_id": "Tanımlayıcı ekle",
|
||||||
"form_client_name": "İstemci ismi girin",
|
"form_client_name": "İstemci ismi girin",
|
||||||
"name": "Ad",
|
"name": "Adı",
|
||||||
"client_global_settings": "Genel ayarları kullan",
|
"client_global_settings": "Genel ayarları kullan",
|
||||||
"client_deleted": "\"{{key}}\" istemcisi başarıyla silindi",
|
"client_deleted": "\"{{key}}\" istemcisi başarıyla silindi",
|
||||||
"client_added": "\"{{key}}\" istemcisi başarıyla eklendi",
|
"client_added": "\"{{key}}\" istemcisi başarıyla eklendi",
|
||||||
|
@ -143,7 +143,6 @@
|
|||||||
"enforced_save_search": "Примусовий безпечний пошук",
|
"enforced_save_search": "Примусовий безпечний пошук",
|
||||||
"number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
|
"number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
|
||||||
"average_processing_time": "Середній час обробки",
|
"average_processing_time": "Середній час обробки",
|
||||||
"processing_time": "Час обробки",
|
|
||||||
"average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
|
"average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
|
||||||
"block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів",
|
"block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів",
|
||||||
"filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
|
"filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
|
||||||
@ -683,7 +682,7 @@
|
|||||||
"disable_notify_for_minutes_plural": "Вимкнення захисту на {{count}} хвилин",
|
"disable_notify_for_minutes_plural": "Вимкнення захисту на {{count}} хвилин",
|
||||||
"disable_notify_for_hours": "Вимкнення захисту на {{count}} годину",
|
"disable_notify_for_hours": "Вимкнення захисту на {{count}} годину",
|
||||||
"disable_notify_for_hours_plural": "Вимкнення захисту на {{count}} годин",
|
"disable_notify_for_hours_plural": "Вимкнення захисту на {{count}} годин",
|
||||||
"disable_notify_until_tomorrow": "Відключення захисту до завтра",
|
"disable_notify_until_tomorrow": "Вимкнути захист до завтра",
|
||||||
"enable_protection_timer": "Захист буде ввімкнено о {{time}}",
|
"enable_protection_timer": "Захист буде ввімкнено о {{time}}",
|
||||||
"custom_retention_input": "Введіть час в годинах",
|
"custom_retention_input": "Введіть час в годинах",
|
||||||
"custom_rotation_input": "Введіть час в годинах",
|
"custom_rotation_input": "Введіть час в годинах",
|
||||||
@ -701,7 +700,7 @@
|
|||||||
"schedule_current_timezone": "Поточний часовий пояс: {{value}}",
|
"schedule_current_timezone": "Поточний часовий пояс: {{value}}",
|
||||||
"schedule_time_all_day": "Увесь день",
|
"schedule_time_all_day": "Увесь день",
|
||||||
"schedule_modal_description": "Цей розклад замінить усі наявні розклади на той самий день тижня. Кожен день тижня може мати тільки один період бездіяльності.",
|
"schedule_modal_description": "Цей розклад замінить усі наявні розклади на той самий день тижня. Кожен день тижня може мати тільки один період бездіяльності.",
|
||||||
"schedule_modal_time_off": "Блокування сервісів відключена:",
|
"schedule_modal_time_off": "Вимкнення блокування сервісів:",
|
||||||
"schedule_new": "Новий розклад",
|
"schedule_new": "Новий розклад",
|
||||||
"schedule_edit": "Редагувати розклад",
|
"schedule_edit": "Редагувати розклад",
|
||||||
"schedule_save": "Зберегти розклад",
|
"schedule_save": "Зберегти розклад",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Cài đặt thiết bị",
|
"client_settings": "Cài đặt thiết bị",
|
||||||
"example_upstream_reserved": "ngược dòng <0>cho các miền cụ thể</0>;",
|
"example_upstream_reserved": "ngược dòng <0>cho các miền cụ thể</0>;",
|
||||||
|
"example_multiple_upstreams_reserved": "nhiều máy chủ thượng nguồn <0>cho các miền cụ thể</0>;",
|
||||||
"example_upstream_comment": "một lời bình luận.",
|
"example_upstream_comment": "một lời bình luận.",
|
||||||
"upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến",
|
"upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến",
|
||||||
"parallel_requests": "Yêu cầu song song",
|
"parallel_requests": "Yêu cầu song song",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "Bắt buộc tìm kiếm an toàn",
|
"enforced_save_search": "Bắt buộc tìm kiếm an toàn",
|
||||||
"number_of_dns_query_to_safe_search": "Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn",
|
"number_of_dns_query_to_safe_search": "Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn",
|
||||||
"average_processing_time": "Thời gian xử lý trung bình",
|
"average_processing_time": "Thời gian xử lý trung bình",
|
||||||
"processing_time": "Thời gian xử lý",
|
"average_upstream_response_time": "Thời gian phản hồi trung bình từ máy chủ thượng nguồn",
|
||||||
|
"response_time": "Thời gian đáp ứng",
|
||||||
"average_processing_time_hint": "Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây",
|
"average_processing_time_hint": "Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây",
|
||||||
"block_domain_use_filters_and_hosts": "Chặn tên miền sử dụng các bộ lọc và file hosts",
|
"block_domain_use_filters_and_hosts": "Chặn tên miền sử dụng các bộ lọc và file hosts",
|
||||||
"filters_block_toggle_hint": "Bạn có thể thiết lập quy tắc chặn tại cài đặt <a>Bộ lọc</a>.",
|
"filters_block_toggle_hint": "Bạn có thể thiết lập quy tắc chặn tại cài đặt <a>Bộ lọc</a>.",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "客户端设置",
|
"client_settings": "客户端设置",
|
||||||
"example_upstream_reserved": "指定为<0>特定域名</0>的上游服务器;",
|
"example_upstream_reserved": "指定为<0>特定域名</0>的上游服务器;",
|
||||||
|
"example_multiple_upstreams_reserved": "<0>特定域名</0>的多个上游服务器;",
|
||||||
"example_upstream_comment": "注释。",
|
"example_upstream_comment": "注释。",
|
||||||
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
|
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
|
||||||
"parallel_requests": "并行请求",
|
"parallel_requests": "并行请求",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "强制安全搜索",
|
"enforced_save_search": "强制安全搜索",
|
||||||
"number_of_dns_query_to_safe_search": "启用强制安全搜索后对搜索引擎的 DNS 请求总数",
|
"number_of_dns_query_to_safe_search": "启用强制安全搜索后对搜索引擎的 DNS 请求总数",
|
||||||
"average_processing_time": "平均处理时间",
|
"average_processing_time": "平均处理时间",
|
||||||
"processing_time": "处理时间",
|
"average_upstream_response_time": "上游服务器的平均响应时间",
|
||||||
|
"response_time": "响应时间",
|
||||||
"average_processing_time_hint": "处理 DNS 请求的平均时间(毫秒)",
|
"average_processing_time_hint": "处理 DNS 请求的平均时间(毫秒)",
|
||||||
"block_domain_use_filters_and_hosts": "使用过滤器和 Hosts 文件以拦截指定域名",
|
"block_domain_use_filters_and_hosts": "使用过滤器和 Hosts 文件以拦截指定域名",
|
||||||
"filters_block_toggle_hint": "你可以在 <a>过滤器</a> 设置中添加过滤规则。",
|
"filters_block_toggle_hint": "你可以在 <a>过滤器</a> 设置中添加过滤规则。",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "用戶端設定",
|
"client_settings": "用戶端設定",
|
||||||
"example_upstream_reserved": "<0>供特定的網域</0>之上游;",
|
"example_upstream_reserved": "<0>供特定的網域</0>之上游;",
|
||||||
|
"example_multiple_upstreams_reserved": "<0>特定網域</0>的多個上游伺服器;",
|
||||||
"example_upstream_comment": "註解。",
|
"example_upstream_comment": "註解。",
|
||||||
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
|
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
|
||||||
"parallel_requests": "並行的請求",
|
"parallel_requests": "並行的請求",
|
||||||
@ -8,9 +9,9 @@
|
|||||||
"load_balancing_desc": "每次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機的演算法來選擇伺服器,以便最快的伺服器被更常使用。",
|
"load_balancing_desc": "每次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機的演算法來選擇伺服器,以便最快的伺服器被更常使用。",
|
||||||
"bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器",
|
"bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器",
|
||||||
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
|
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
|
||||||
"fallback_dns_title": "備援 DNS 伺服器",
|
"fallback_dns_title": "應變 DNS 伺服器",
|
||||||
"fallback_dns_desc": "當上游 DNS 伺服器未回應時使用的備援 DNS 伺服器清單。語法與上面的主要上游欄位相同。",
|
"fallback_dns_desc": "當上游 DNS 伺服器未回覆時被使用的應變 DNS 伺服器之清單。此語法與在上面主要上游欄位中的相同。",
|
||||||
"fallback_dns_placeholder": "每行輸入一個備援 DNS 伺服器",
|
"fallback_dns_placeholder": "每行輸入一個應變 DNS 伺服器",
|
||||||
"local_ptr_title": "私人反向的 DNS 伺服器",
|
"local_ptr_title": "私人反向的 DNS 伺服器",
|
||||||
"local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
|
"local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
|
||||||
"local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。",
|
"local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。",
|
||||||
@ -131,7 +132,7 @@
|
|||||||
"top_clients": "熱門用戶端",
|
"top_clients": "熱門用戶端",
|
||||||
"no_clients_found": "無已發現之用戶端",
|
"no_clients_found": "無已發現之用戶端",
|
||||||
"general_statistics": "一般的統計資料",
|
"general_statistics": "一般的統計資料",
|
||||||
"top_upstreams": "經常請求的上游伺服器",
|
"top_upstreams": "熱門上游",
|
||||||
"no_upstreams_data_found": "找不到上游伺服器資料",
|
"no_upstreams_data_found": "找不到上游伺服器資料",
|
||||||
"number_of_dns_query_days": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量",
|
"number_of_dns_query_days": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量",
|
||||||
"number_of_dns_query_days_plural": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量",
|
"number_of_dns_query_days_plural": "在最近的 {{count}} 日內已處理的 DNS 查詢之數量",
|
||||||
@ -143,7 +144,8 @@
|
|||||||
"enforced_save_search": "已強制執行的安全搜尋",
|
"enforced_save_search": "已強制執行的安全搜尋",
|
||||||
"number_of_dns_query_to_safe_search": "安全搜尋已被強制執行之屬於搜尋引擎的 DNS 請求之數量",
|
"number_of_dns_query_to_safe_search": "安全搜尋已被強制執行之屬於搜尋引擎的 DNS 請求之數量",
|
||||||
"average_processing_time": "平均的處理時間",
|
"average_processing_time": "平均的處理時間",
|
||||||
"processing_time": "處理時間",
|
"average_upstream_response_time": "平均的上游回應時間",
|
||||||
|
"response_time": "回應時間",
|
||||||
"average_processing_time_hint": "在處理一項 DNS 請求時以毫秒(ms)計的平均時間",
|
"average_processing_time_hint": "在處理一項 DNS 請求時以毫秒(ms)計的平均時間",
|
||||||
"block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域",
|
"block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域",
|
||||||
"filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設置封鎖規則。",
|
"filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設置封鎖規則。",
|
||||||
@ -288,7 +290,7 @@
|
|||||||
"blocking_ipv4": "封鎖 IPv4",
|
"blocking_ipv4": "封鎖 IPv4",
|
||||||
"blocking_ipv6": "封鎖 IPv6",
|
"blocking_ipv6": "封鎖 IPv6",
|
||||||
"blocked_response_ttl": "已封鎖的回應之存活時間(TTL)",
|
"blocked_response_ttl": "已封鎖的回應之存活時間(TTL)",
|
||||||
"blocked_response_ttl_desc": "指定客戶端應將過濾後的回應存入快取的秒數",
|
"blocked_response_ttl_desc": "對用戶端應快取受過濾的回應,指定多少秒數",
|
||||||
"form_enter_blocked_response_ttl": "請輸入已封鎖回應的存活時間(秒)",
|
"form_enter_blocked_response_ttl": "請輸入已封鎖回應的存活時間(秒)",
|
||||||
"dnscrypt": "DNSCrypt",
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
|
@ -118,6 +118,11 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-body--filters {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-body__item:not(:first-child) {
|
.modal-body__item:not(:first-child) {
|
||||||
padding-top: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ const UpstreamAvgTime = ({
|
|||||||
subtitle,
|
subtitle,
|
||||||
}) => (
|
}) => (
|
||||||
<Card
|
<Card
|
||||||
title={t('average_processing_time')}
|
title={t('average_upstream_response_time')}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
bodyType="card-table"
|
bodyType="card-table"
|
||||||
refresh={refreshButton}
|
refresh={refreshButton}
|
||||||
@ -55,7 +55,7 @@ const UpstreamAvgTime = ({
|
|||||||
Cell: DomainCell,
|
Cell: DomainCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <Trans>processing_time</Trans>,
|
Header: <Trans>response_time</Trans>,
|
||||||
accessor: 'count',
|
accessor: 'count',
|
||||||
maxWidth: 190,
|
maxWidth: 190,
|
||||||
Cell: TimeCell,
|
Cell: TimeCell,
|
||||||
|
@ -28,7 +28,7 @@ const renderIcons = (iconsData) => iconsData.map(({
|
|||||||
}) => <a key={iconName} href={href} target="_blank" rel="noopener noreferrer"
|
}) => <a key={iconName} href={href} target="_blank" rel="noopener noreferrer"
|
||||||
className={classNames('d-flex align-items-center', className)}
|
className={classNames('d-flex align-items-center', className)}
|
||||||
>
|
>
|
||||||
<svg className="nav-icon nav-icon--gray">
|
<svg className="icon icon--15 mr-1 icon--gray">
|
||||||
<use xlinkHref={`#${iconName}`} />
|
<use xlinkHref={`#${iconName}`} />
|
||||||
</svg>
|
</svg>
|
||||||
</a>);
|
</a>);
|
||||||
@ -110,7 +110,7 @@ const Form = (props) => {
|
|||||||
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
|
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
|
||||||
|
|
||||||
return <form onSubmit={handleSubmit}>
|
return <form onSubmit={handleSubmit}>
|
||||||
<div className="modal-body modal-body--medium">
|
<div className="modal-body modal-body--filters">
|
||||||
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE
|
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE
|
||||||
&& <div className="d-flex justify-content-around">
|
&& <div className="d-flex justify-content-around">
|
||||||
<button onClick={openFilteringListModal}
|
<button onClick={openFilteringListModal}
|
||||||
|
@ -81,6 +81,10 @@ export const Modal = ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (timezone !== intialTimezone) {
|
||||||
|
newSchedule.time_zone = timezone;
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(newSchedule);
|
onSubmit(newSchedule);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
color: var(--gray-f3);
|
color: var(--gray-f3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__text--client {
|
.logs__table .logs__text--client {
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +137,22 @@ const Examples = (props) => (
|
|||||||
example_upstream_reserved
|
example_upstream_reserved
|
||||||
</Trans>
|
</Trans>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>[/example.local/]94.140.14.140 2a10:50c0::1:ff</code>: <Trans
|
||||||
|
components={[
|
||||||
|
<a
|
||||||
|
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
|
Link
|
||||||
|
</a>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
example_multiple_upstreams_reserved
|
||||||
|
</Trans>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<code>{COMMENT_LINE_DEFAULT_TOKEN} comment</code>: <Trans>
|
<code>{COMMENT_LINE_DEFAULT_TOKEN} comment</code>: <Trans>
|
||||||
example_upstream_comment
|
example_upstream_comment
|
||||||
|
@ -149,3 +149,7 @@
|
|||||||
.card .logs__row--blue {
|
.card .logs__row--blue {
|
||||||
background-color: #ecf7ff;
|
background-color: #ecf7ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .card .logs__row--blue {
|
||||||
|
background-color: var(--logs__row--blue-bgcolor);
|
||||||
|
}
|
||||||
|
@ -24,6 +24,13 @@
|
|||||||
height: var(--size);
|
height: var(--size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon--15 {
|
||||||
|
--size: 0.95rem;
|
||||||
|
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
.icon--gray {
|
.icon--gray {
|
||||||
color: var(--gray-a5);
|
color: var(--gray-a5);
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,6 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ReactTable .rt-tbody {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactTable .rt-noData {
|
.ReactTable .rt-noData {
|
||||||
color: var(--rt-nodata-color);
|
color: var(--rt-nodata-color);
|
||||||
background-color: var(--rt-nodata-bgcolor);
|
background-color: var(--rt-nodata-bgcolor);
|
||||||
|
@ -202,12 +202,30 @@ export default {
|
|||||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"
|
||||||
},
|
},
|
||||||
|
"hagezi_pro": {
|
||||||
|
"name": "HaGeZi's Pro Blocklist",
|
||||||
|
"categoryId": "general",
|
||||||
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_48.txt"
|
||||||
|
},
|
||||||
|
"hagezi_pro++": {
|
||||||
|
"name": "HaGeZi's Pro++ Blocklist",
|
||||||
|
"categoryId": "general",
|
||||||
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt"
|
||||||
|
},
|
||||||
"hagezi_threat_intelligence_feeds": {
|
"hagezi_threat_intelligence_feeds": {
|
||||||
"name": "HaGeZi's Threat Intelligence Feeds",
|
"name": "HaGeZi's Threat Intelligence Feeds",
|
||||||
"categoryId": "security",
|
"categoryId": "security",
|
||||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_44.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_44.txt"
|
||||||
},
|
},
|
||||||
|
"hagezi_ultimate": {
|
||||||
|
"name": "HaGeZi's Ultimate Blocklist",
|
||||||
|
"categoryId": "general",
|
||||||
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"
|
||||||
|
},
|
||||||
"no_google": {
|
"no_google": {
|
||||||
"name": "No Google",
|
"name": "No Google",
|
||||||
"categoryId": "other",
|
"categoryId": "other",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"timeUpdated": "2023-10-15T12:13:01.838Z",
|
"timeUpdated": "2023-11-10T12:55:56.663Z",
|
||||||
"categories": {
|
"categories": {
|
||||||
"0": "audio_video_player",
|
"0": "audio_video_player",
|
||||||
"1": "comments",
|
"1": "comments",
|
||||||
@ -681,8 +681,9 @@
|
|||||||
"adcolony": {
|
"adcolony": {
|
||||||
"name": "AdColony",
|
"name": "AdColony",
|
||||||
"categoryId": 4,
|
"categoryId": 4,
|
||||||
"url": "thttp://www.admarvel.com/",
|
"url": "https://www.adcolony.com/history-of-adcolony/",
|
||||||
"companyId": "adcolony"
|
"companyId": "digital_turbine",
|
||||||
|
"source": "AdGuard"
|
||||||
},
|
},
|
||||||
"adconion": {
|
"adconion": {
|
||||||
"name": "Adconion",
|
"name": "Adconion",
|
||||||
@ -1294,6 +1295,13 @@
|
|||||||
"url": "http://www.demdex.com/",
|
"url": "http://www.demdex.com/",
|
||||||
"companyId": "adobe"
|
"companyId": "adobe"
|
||||||
},
|
},
|
||||||
|
"adobe_developer": {
|
||||||
|
"name": "Adobe Developer",
|
||||||
|
"categoryId": 8,
|
||||||
|
"url": "https://developer.adobe.com/",
|
||||||
|
"companyId": "adobe",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"adobe_dynamic_media": {
|
"adobe_dynamic_media": {
|
||||||
"name": "Adobe Dynamic Media",
|
"name": "Adobe Dynamic Media",
|
||||||
"categoryId": 4,
|
"categoryId": 4,
|
||||||
@ -1309,8 +1317,9 @@
|
|||||||
"adobe_experience_cloud": {
|
"adobe_experience_cloud": {
|
||||||
"name": "Adobe Experience Cloud",
|
"name": "Adobe Experience Cloud",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
"url": "https://www.adobe.com/experience-cloud.html",
|
"url": "https://business.adobe.com/",
|
||||||
"companyId": "adobe"
|
"companyId": "adobe",
|
||||||
|
"source": "AdGuard"
|
||||||
},
|
},
|
||||||
"adobe_login": {
|
"adobe_login": {
|
||||||
"name": "Adobe Login",
|
"name": "Adobe Login",
|
||||||
@ -4353,6 +4362,13 @@
|
|||||||
"url": "http://chartbeat.com/",
|
"url": "http://chartbeat.com/",
|
||||||
"companyId": "chartbeat"
|
"companyId": "chartbeat"
|
||||||
},
|
},
|
||||||
|
"chartboost": {
|
||||||
|
"name": "Chartboost",
|
||||||
|
"categoryId": 4,
|
||||||
|
"url": "http://chartboost.com/",
|
||||||
|
"companyId": "take-two",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"chaser": {
|
"chaser": {
|
||||||
"name": "Chaser",
|
"name": "Chaser",
|
||||||
"categoryId": 2,
|
"categoryId": 2,
|
||||||
@ -7330,7 +7346,7 @@
|
|||||||
"name": "Flurry",
|
"name": "Flurry",
|
||||||
"categoryId": 101,
|
"categoryId": 101,
|
||||||
"url": "http://www.flurry.com/",
|
"url": "http://www.flurry.com/",
|
||||||
"companyId": "verizon",
|
"companyId": "apollo_global_management",
|
||||||
"source": "AdGuard"
|
"source": "AdGuard"
|
||||||
},
|
},
|
||||||
"flxone": {
|
"flxone": {
|
||||||
@ -12386,6 +12402,13 @@
|
|||||||
"url": "http://www.nimblecommerce.com/",
|
"url": "http://www.nimblecommerce.com/",
|
||||||
"companyId": "nimblecommerce"
|
"companyId": "nimblecommerce"
|
||||||
},
|
},
|
||||||
|
"nine_direct_digital": {
|
||||||
|
"name": "Nine Digital Direct",
|
||||||
|
"categoryId": 4,
|
||||||
|
"url": "https://ninedigitaldirect.com.au/",
|
||||||
|
"companyId": "nine_entertainment",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"ninja_access_analysis": {
|
"ninja_access_analysis": {
|
||||||
"name": "Ninja Access Analysis",
|
"name": "Ninja Access Analysis",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
@ -19546,8 +19569,9 @@
|
|||||||
"yahoo": {
|
"yahoo": {
|
||||||
"name": "Yahoo!",
|
"name": "Yahoo!",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
"url": "https://yahoo.com",
|
"url": "https://yahoo.com/",
|
||||||
"companyId": "verizon"
|
"companyId": "apollo_global_management",
|
||||||
|
"source": "AdGuard"
|
||||||
},
|
},
|
||||||
"yahoo_ad_exchange": {
|
"yahoo_ad_exchange": {
|
||||||
"name": "Yahoo! Ad Exchange",
|
"name": "Yahoo! Ad Exchange",
|
||||||
@ -19561,6 +19585,13 @@
|
|||||||
"url": "https://developer.yahoo.com/analytics/",
|
"url": "https://developer.yahoo.com/analytics/",
|
||||||
"companyId": "verizon"
|
"companyId": "verizon"
|
||||||
},
|
},
|
||||||
|
"yahoo_advertising": {
|
||||||
|
"name": "Yahoo! Advertising",
|
||||||
|
"categoryId": 4,
|
||||||
|
"url": "https://www.advertising.yahooinc.com/",
|
||||||
|
"companyId": "apollo_global_management",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"yahoo_analytics": {
|
"yahoo_analytics": {
|
||||||
"name": "Yahoo! Analytics",
|
"name": "Yahoo! Analytics",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
@ -19591,6 +19622,13 @@
|
|||||||
"url": "http://searchmarketing.yahoo.com",
|
"url": "http://searchmarketing.yahoo.com",
|
||||||
"companyId": "verizon"
|
"companyId": "verizon"
|
||||||
},
|
},
|
||||||
|
"yahoo_search": {
|
||||||
|
"name": "Yahoo! Search",
|
||||||
|
"categoryId": 4,
|
||||||
|
"url": "https://search.yahooinc.com/",
|
||||||
|
"companyId": "apollo_global_management",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"yahoo_small_business": {
|
"yahoo_small_business": {
|
||||||
"name": "Yahoo! Small Business",
|
"name": "Yahoo! Small Business",
|
||||||
"categoryId": 4,
|
"categoryId": 4,
|
||||||
@ -20228,6 +20266,7 @@
|
|||||||
"ad-cloud.jp": "adcloud",
|
"ad-cloud.jp": "adcloud",
|
||||||
"admarvel.s3.amazonaws.com": "adcolony",
|
"admarvel.s3.amazonaws.com": "adcolony",
|
||||||
"ads.admarvel.com": "adcolony",
|
"ads.admarvel.com": "adcolony",
|
||||||
|
"adcolony.com": "adcolony",
|
||||||
"adrdgt.com": "adconion",
|
"adrdgt.com": "adconion",
|
||||||
"amgdgt.com": "adconion",
|
"amgdgt.com": "adconion",
|
||||||
"adcrowd.com": "adcrowd",
|
"adcrowd.com": "adcrowd",
|
||||||
@ -20382,6 +20421,7 @@
|
|||||||
"demdex.net": "adobe_audience_manager",
|
"demdex.net": "adobe_audience_manager",
|
||||||
"everestjs.net": "adobe_audience_manager",
|
"everestjs.net": "adobe_audience_manager",
|
||||||
"everesttech.net": "adobe_audience_manager",
|
"everesttech.net": "adobe_audience_manager",
|
||||||
|
"adobe.io": "adobe_developer",
|
||||||
"scene7.com": "adobe_dynamic_media",
|
"scene7.com": "adobe_dynamic_media",
|
||||||
"adobedtm.com": "adobe_dynamic_tag_management",
|
"adobedtm.com": "adobe_dynamic_tag_management",
|
||||||
"2o7.net": "adobe_experience_cloud",
|
"2o7.net": "adobe_experience_cloud",
|
||||||
@ -20654,8 +20694,10 @@
|
|||||||
"a2z.com": "amazon",
|
"a2z.com": "amazon",
|
||||||
"aamazoncognito.com": "amazon",
|
"aamazoncognito.com": "amazon",
|
||||||
"amazon-corp.com": "amazon",
|
"amazon-corp.com": "amazon",
|
||||||
|
"amazon-dss.com": "amazon",
|
||||||
"amazon.com.au": "amazon",
|
"amazon.com.au": "amazon",
|
||||||
"amazon.com.mx": "amazon",
|
"amazon.com.mx": "amazon",
|
||||||
|
"amazon.dev": "amazon",
|
||||||
"amazon.in": "amazon",
|
"amazon.in": "amazon",
|
||||||
"amazon.nl": "amazon",
|
"amazon.nl": "amazon",
|
||||||
"amazon.sa": "amazon",
|
"amazon.sa": "amazon",
|
||||||
@ -20663,6 +20705,7 @@
|
|||||||
"amazonbrowserapp.es": "amazon",
|
"amazonbrowserapp.es": "amazon",
|
||||||
"amazoncrl.com": "amazon",
|
"amazoncrl.com": "amazon",
|
||||||
"firetvcaptiveportal.com": "amazon",
|
"firetvcaptiveportal.com": "amazon",
|
||||||
|
"ntp-fireos.com": "amazon",
|
||||||
"amazon-adsystem.com": "amazon_adsystem",
|
"amazon-adsystem.com": "amazon_adsystem",
|
||||||
"serving-sys.com": "amazon_adsystem",
|
"serving-sys.com": "amazon_adsystem",
|
||||||
"sizmek.com": "amazon_adsystem",
|
"sizmek.com": "amazon_adsystem",
|
||||||
@ -20678,12 +20721,16 @@
|
|||||||
"amazontrust.com": "amazon_cdn",
|
"amazontrust.com": "amazon_cdn",
|
||||||
"associates-amazon.com": "amazon_cdn",
|
"associates-amazon.com": "amazon_cdn",
|
||||||
"cloudfront.net": "amazon_cloudfront",
|
"cloudfront.net": "amazon_cloudfront",
|
||||||
|
"ota-cloudfront.net": "amazon_cloudfront",
|
||||||
"axx-eu.amazon-adsystem.com": "amazon_mobile_ads",
|
"axx-eu.amazon-adsystem.com": "amazon_mobile_ads",
|
||||||
"amazonpay.com": "amazon_payments",
|
"amazonpay.com": "amazon_payments",
|
||||||
"payments-amazon.com": "amazon_payments",
|
"payments-amazon.com": "amazon_payments",
|
||||||
"amazonpay.in": "amazon_payments",
|
"amazonpay.in": "amazon_payments",
|
||||||
"aiv-cdn.net": "amazon_video",
|
"aiv-cdn.net": "amazon_video",
|
||||||
|
"aiv-delivery.net": "amazon_video",
|
||||||
"amazonvideo.com": "amazon_video",
|
"amazonvideo.com": "amazon_video",
|
||||||
|
"pv-cdn.net": "amazon_video",
|
||||||
|
"primevideo.com": "amazon_video",
|
||||||
"amazonaws.com": "amazon_web_services",
|
"amazonaws.com": "amazon_web_services",
|
||||||
"amazonwebservices.com": "amazon_web_services",
|
"amazonwebservices.com": "amazon_web_services",
|
||||||
"awsstatic.com": "amazon_web_services",
|
"awsstatic.com": "amazon_web_services",
|
||||||
@ -20830,6 +20877,8 @@
|
|||||||
"ad.globe7.com": "axill",
|
"ad.globe7.com": "axill",
|
||||||
"azadify.com": "azadify",
|
"azadify.com": "azadify",
|
||||||
"azure.com": "azure",
|
"azure.com": "azure",
|
||||||
|
"azure.net": "azure",
|
||||||
|
"azurefd.net": "azure",
|
||||||
"trafficmanager.net": "azure",
|
"trafficmanager.net": "azure",
|
||||||
"blob.core.windows.net": "azure_blob_storage",
|
"blob.core.windows.net": "azure_blob_storage",
|
||||||
"azureedge.net": "azureedge.net",
|
"azureedge.net": "azureedge.net",
|
||||||
@ -21120,6 +21169,7 @@
|
|||||||
"chaordicsystems.com": "chaordic",
|
"chaordicsystems.com": "chaordic",
|
||||||
"chartbeat.com": "chartbeat",
|
"chartbeat.com": "chartbeat",
|
||||||
"chartbeat.net": "chartbeat",
|
"chartbeat.net": "chartbeat",
|
||||||
|
"chartboost.com": "chartboost",
|
||||||
"chaser.ru": "chaser",
|
"chaser.ru": "chaser",
|
||||||
"cloud.chatbeacon.io": "chat_beacon",
|
"cloud.chatbeacon.io": "chat_beacon",
|
||||||
"chatango.com": "chatango",
|
"chatango.com": "chatango",
|
||||||
@ -21929,7 +21979,9 @@
|
|||||||
"githubassets.com": "github",
|
"githubassets.com": "github",
|
||||||
"githubusercontent.com": "github",
|
"githubusercontent.com": "github",
|
||||||
"ghcr.io": "github",
|
"ghcr.io": "github",
|
||||||
|
"github.blog": "github",
|
||||||
"github.dev": "github",
|
"github.dev": "github",
|
||||||
|
"octocaptcha.com": "github",
|
||||||
"githubapp.com": "github_apps",
|
"githubapp.com": "github_apps",
|
||||||
"github.io": "github_pages",
|
"github.io": "github_pages",
|
||||||
"aff3.gittigidiyor.com": "gittigidiyor_affiliate_program",
|
"aff3.gittigidiyor.com": "gittigidiyor_affiliate_program",
|
||||||
@ -23088,7 +23140,13 @@
|
|||||||
"s-microsoft.com": "microsoft",
|
"s-microsoft.com": "microsoft",
|
||||||
"trouter.io": "microsoft",
|
"trouter.io": "microsoft",
|
||||||
"windows.net": "microsoft",
|
"windows.net": "microsoft",
|
||||||
|
"bingapis.com": "microsoft",
|
||||||
|
"msauth.net": "microsoft",
|
||||||
|
"msftauth.net": "microsoft",
|
||||||
|
"msftstatic.com": "microsoft",
|
||||||
"msidentity.com": "microsoft",
|
"msidentity.com": "microsoft",
|
||||||
|
"nelreports.net": "microsoft",
|
||||||
|
"windowscentral.com": "microsoft",
|
||||||
"analytics.live.com": "microsoft_analytics",
|
"analytics.live.com": "microsoft_analytics",
|
||||||
"a.clarity.ms": "microsoft_clarity",
|
"a.clarity.ms": "microsoft_clarity",
|
||||||
"b.clarity.ms": "microsoft_clarity",
|
"b.clarity.ms": "microsoft_clarity",
|
||||||
@ -23173,6 +23231,7 @@
|
|||||||
"mrpdata.com": "mrpdata",
|
"mrpdata.com": "mrpdata",
|
||||||
"mrpdata.net": "mrpdata",
|
"mrpdata.net": "mrpdata",
|
||||||
"mrskincash.com": "mrskincash",
|
"mrskincash.com": "mrskincash",
|
||||||
|
"a-msedge.net": "msedge",
|
||||||
"e-msedge.net": "msedge",
|
"e-msedge.net": "msedge",
|
||||||
"l-msedge.net": "msedge",
|
"l-msedge.net": "msedge",
|
||||||
"s-msedge.net": "msedge",
|
"s-msedge.net": "msedge",
|
||||||
@ -23294,6 +23353,7 @@
|
|||||||
"ads.ngageinc.com": "ngage_inc.",
|
"ads.ngageinc.com": "ngage_inc.",
|
||||||
"nice264.com": "nice264.com",
|
"nice264.com": "nice264.com",
|
||||||
"nimblecommerce.com": "nimblecommerce",
|
"nimblecommerce.com": "nimblecommerce",
|
||||||
|
"nineanalytics.io": "nine_direct_digital",
|
||||||
"cho-chin.com": "ninja_access_analysis",
|
"cho-chin.com": "ninja_access_analysis",
|
||||||
"donburako.com": "ninja_access_analysis",
|
"donburako.com": "ninja_access_analysis",
|
||||||
"hishaku.com": "ninja_access_analysis",
|
"hishaku.com": "ninja_access_analysis",
|
||||||
@ -24947,10 +25007,15 @@
|
|||||||
"yahoo.com": "yahoo",
|
"yahoo.com": "yahoo",
|
||||||
"yahooapis.com": "yahoo",
|
"yahooapis.com": "yahoo",
|
||||||
"yimg.com": "yahoo",
|
"yimg.com": "yahoo",
|
||||||
"ads.yahoo.com": "yahoo_ad_exchange",
|
"oath.cloud": "yahoo",
|
||||||
|
"yahoo.net": "yahoo",
|
||||||
|
"yahooinc.com": "yahoo",
|
||||||
|
"yahoodns.net": "yahoo",
|
||||||
"yads.yahoo.com": "yahoo_ad_exchange",
|
"yads.yahoo.com": "yahoo_ad_exchange",
|
||||||
"yieldmanager.com": "yahoo_ad_exchange",
|
"yieldmanager.com": "yahoo_ad_exchange",
|
||||||
"pr-bh.ybp.yahoo.com": "yahoo_ad_manager",
|
"pr-bh.ybp.yahoo.com": "yahoo_ad_manager",
|
||||||
|
"ads.yahoo.com": "yahoo_advertising",
|
||||||
|
"adtech.yahooinc.com": "yahoo_advertising",
|
||||||
"analytics.yahoo.com": "yahoo_analytics",
|
"analytics.yahoo.com": "yahoo_analytics",
|
||||||
"np.lexity.com": "yahoo_commerce_central",
|
"np.lexity.com": "yahoo_commerce_central",
|
||||||
"storage-yahoo.jp": "yahoo_japan_retargeting",
|
"storage-yahoo.jp": "yahoo_japan_retargeting",
|
||||||
@ -24960,6 +25025,7 @@
|
|||||||
"yjtag.jp": "yahoo_japan_retargeting",
|
"yjtag.jp": "yahoo_japan_retargeting",
|
||||||
"ov.yahoo.co.jp": "yahoo_overture",
|
"ov.yahoo.co.jp": "yahoo_overture",
|
||||||
"overture.com": "yahoo_overture",
|
"overture.com": "yahoo_overture",
|
||||||
|
"search.yahooinc.com": "yahoo_search",
|
||||||
"luminate.com": "yahoo_small_business",
|
"luminate.com": "yahoo_small_business",
|
||||||
"pixazza.com": "yahoo_small_business",
|
"pixazza.com": "yahoo_small_business",
|
||||||
"awaps.yandex.ru": "yandex",
|
"awaps.yandex.ru": "yandex",
|
||||||
|
12
go.mod
12
go.mod
@ -3,9 +3,9 @@ module github.com/AdguardTeam/AdGuardHome
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.56.2
|
github.com/AdguardTeam/dnsproxy v0.57.3
|
||||||
github.com/AdguardTeam/golibs v0.17.1
|
github.com/AdguardTeam/golibs v0.17.2
|
||||||
github.com/AdguardTeam/urlfilter v0.17.0
|
github.com/AdguardTeam/urlfilter v0.17.3
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||||
github.com/bluele/gcache v0.0.2
|
github.com/bluele/gcache v0.0.2
|
||||||
@ -17,7 +17,7 @@ require (
|
|||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/renameio/v2 v2.0.0
|
github.com/google/renameio/v2 v2.0.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.3.1
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c
|
||||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||||
github.com/kardianos/service v1.2.2
|
github.com/kardianos/service v1.2.2
|
||||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||||
@ -27,9 +27,9 @@ require (
|
|||||||
// own code for that. Perhaps, use gopacket.
|
// own code for that. Perhaps, use gopacket.
|
||||||
github.com/mdlayher/raw v0.1.0
|
github.com/mdlayher/raw v0.1.0
|
||||||
github.com/miekg/dns v1.1.56
|
github.com/miekg/dns v1.1.56
|
||||||
github.com/quic-go/quic-go v0.39.1
|
github.com/quic-go/quic-go v0.39.2
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/ti-mo/netfilter v0.5.0
|
github.com/ti-mo/netfilter v0.5.1
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||||
|
24
go.sum
24
go.sum
@ -1,9 +1,9 @@
|
|||||||
github.com/AdguardTeam/dnsproxy v0.56.2 h1:+k1iUmp05QIqkgXWyPn70fki4FouHe6vHIyHguelKao=
|
github.com/AdguardTeam/dnsproxy v0.57.3 h1:0v7D+LQrOL2k2fvkG3Ft3Cn3ayUsvAdlOlJR+gLxSGA=
|
||||||
github.com/AdguardTeam/dnsproxy v0.56.2/go.mod h1:ZvkbM71HwpilgkCnTubDiR4Ba6x5Qvnhy2iasMWaTDM=
|
github.com/AdguardTeam/dnsproxy v0.57.3/go.mod h1:ZvkbM71HwpilgkCnTubDiR4Ba6x5Qvnhy2iasMWaTDM=
|
||||||
github.com/AdguardTeam/golibs v0.17.1 h1:j3Ehhld5GI/amcHYG+CF0sJ4OOzAQ06BY3N/iBYJZ1M=
|
github.com/AdguardTeam/golibs v0.17.2 h1:vg6wHMjUKscnyPGRvxS5kAt7Uw4YxcJiITZliZ476W8=
|
||||||
github.com/AdguardTeam/golibs v0.17.1/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
|
github.com/AdguardTeam/golibs v0.17.2/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
|
||||||
github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow=
|
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
|
||||||
github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU=
|
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
|
||||||
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/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
@ -49,8 +49,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||||
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/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
@ -94,8 +94,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
|||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.39.1 h1:d/m3oaN/SD2c+f7/yEjZxe2zEVotXprnrCCJ2y/ZZFE=
|
github.com/quic-go/quic-go v0.39.2 h1:hmwAf8zAHlvan0Y5PXxeeBFZEW17IW99sXLry8I2kjk=
|
||||||
github.com/quic-go/quic-go v0.39.1/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
|
github.com/quic-go/quic-go v0.39.2/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -105,8 +105,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
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.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8=
|
github.com/ti-mo/netfilter v0.5.1 h1:cqamEd1c1zmpfpqvInLOro0Znq/RAfw2QL5wL2rAR/8=
|
||||||
github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8=
|
github.com/ti-mo/netfilter v0.5.1/go.mod h1:h9UPQ3ZrTZGBitay+LETMxZvNgWGK/efTUcqES2YiLw=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||||
|
@ -7,11 +7,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/confmigrate"
|
"github.com/AdguardTeam/AdGuardHome/internal/confmigrate"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
// testdata is a virtual filesystem containing test data.
|
// testdata is a virtual filesystem containing test data.
|
||||||
var testdata = os.DirFS("testdata")
|
var testdata = os.DirFS("testdata")
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ func (s *Server) accessListJSON() (j accessListJSON) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAccessList handles requests to the GET /control/access/list endpoint.
|
||||||
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
||||||
aghhttp.WriteJSONResponseOK(w, r, s.accessListJSON())
|
aghhttp.WriteJSONResponseOK(w, r, s.accessListJSON())
|
||||||
}
|
}
|
||||||
@ -224,6 +225,7 @@ func validateStrUniq(clients []string) (uc aghalg.UniqChecker[string], err error
|
|||||||
return uc, uc.Validate()
|
return uc, uc.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAccessSet handles requests to the POST /control/access/set endpoint.
|
||||||
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
||||||
list := &accessListJSON{}
|
list := &accessListJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&list)
|
err := json.NewDecoder(r.Body).Decode(&list)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
)
|
)
|
||||||
@ -151,6 +152,8 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
|||||||
// DNS-over-HTTPS requests, it will return the hostname part of the Host header
|
// DNS-over-HTTPS requests, it will return the hostname part of the Host header
|
||||||
// if there is one.
|
// if there is one.
|
||||||
func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string, err error) {
|
func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string, err error) {
|
||||||
|
from := "tls conn"
|
||||||
|
|
||||||
switch proto {
|
switch proto {
|
||||||
case proxy.ProtoHTTPS:
|
case proxy.ProtoHTTPS:
|
||||||
r := pctx.HTTPRequest
|
r := pctx.HTTPRequest
|
||||||
@ -164,6 +167,7 @@ func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string
|
|||||||
}
|
}
|
||||||
|
|
||||||
srvName = host
|
srvName = host
|
||||||
|
from = "host header"
|
||||||
}
|
}
|
||||||
case proxy.ProtoQUIC:
|
case proxy.ProtoQUIC:
|
||||||
qConn := pctx.QUICConnection
|
qConn := pctx.QUICConnection
|
||||||
@ -183,5 +187,7 @@ func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string
|
|||||||
srvName = tc.ConnectionState().ServerName
|
srvName = tc.ConnectionState().ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug("dnsforward: got client server name %q from %s", srvName, from)
|
||||||
|
|
||||||
return srvName, nil
|
return srvName, nil
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,14 @@ type Config struct {
|
|||||||
// (0 to disable).
|
// (0 to disable).
|
||||||
Ratelimit uint32 `yaml:"ratelimit"`
|
Ratelimit uint32 `yaml:"ratelimit"`
|
||||||
|
|
||||||
|
// RatelimitSubnetLenIPv4 is a subnet length for IPv4 addresses used for
|
||||||
|
// rate limiting requests.
|
||||||
|
RatelimitSubnetLenIPv4 int `yaml:"ratelimit_subnet_len_ipv4"`
|
||||||
|
|
||||||
|
// RatelimitSubnetLenIPv6 is a subnet length for IPv6 addresses used for
|
||||||
|
// rate limiting requests.
|
||||||
|
RatelimitSubnetLenIPv6 int `yaml:"ratelimit_subnet_len_ipv6"`
|
||||||
|
|
||||||
// RatelimitWhitelist is the list of whitelisted client IP addresses.
|
// RatelimitWhitelist is the list of whitelisted client IP addresses.
|
||||||
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"`
|
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"`
|
||||||
|
|
||||||
@ -275,24 +283,26 @@ type ServerConfig struct {
|
|||||||
func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
||||||
srvConf := s.conf
|
srvConf := s.conf
|
||||||
conf = proxy.Config{
|
conf = proxy.Config{
|
||||||
UDPListenAddr: srvConf.UDPListenAddrs,
|
UDPListenAddr: srvConf.UDPListenAddrs,
|
||||||
TCPListenAddr: srvConf.TCPListenAddrs,
|
TCPListenAddr: srvConf.TCPListenAddrs,
|
||||||
HTTP3: srvConf.ServeHTTP3,
|
HTTP3: srvConf.ServeHTTP3,
|
||||||
Ratelimit: int(srvConf.Ratelimit),
|
Ratelimit: int(srvConf.Ratelimit),
|
||||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
RatelimitSubnetMaskIPv4: net.CIDRMask(srvConf.RatelimitSubnetLenIPv4, netutil.IPv4BitLen),
|
||||||
RefuseAny: srvConf.RefuseAny,
|
RatelimitSubnetMaskIPv6: net.CIDRMask(srvConf.RatelimitSubnetLenIPv6, netutil.IPv6BitLen),
|
||||||
TrustedProxies: srvConf.TrustedProxies,
|
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||||
CacheMinTTL: srvConf.CacheMinTTL,
|
RefuseAny: srvConf.RefuseAny,
|
||||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
TrustedProxies: srvConf.TrustedProxies,
|
||||||
CacheOptimistic: srvConf.CacheOptimistic,
|
CacheMinTTL: srvConf.CacheMinTTL,
|
||||||
UpstreamConfig: srvConf.UpstreamConfig,
|
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||||
BeforeRequestHandler: s.beforeRequestHandler,
|
CacheOptimistic: srvConf.CacheOptimistic,
|
||||||
RequestHandler: s.handleDNSRequest,
|
UpstreamConfig: srvConf.UpstreamConfig,
|
||||||
HTTPSServerName: aghhttp.UserAgent(),
|
BeforeRequestHandler: s.beforeRequestHandler,
|
||||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
RequestHandler: s.handleDNSRequest,
|
||||||
MaxGoroutines: int(srvConf.MaxGoroutines),
|
HTTPSServerName: aghhttp.UserAgent(),
|
||||||
UseDNS64: srvConf.UseDNS64,
|
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
MaxGoroutines: int(srvConf.MaxGoroutines),
|
||||||
|
UseDNS64: srvConf.UseDNS64,
|
||||||
|
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if srvConf.EDNSClientSubnet.UseCustom {
|
if srvConf.EDNSClientSubnet.UseCustom {
|
||||||
|
@ -354,9 +354,8 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
|
|||||||
}
|
}
|
||||||
|
|
||||||
dctx := &proxy.DNSContext{
|
dctx := &proxy.DNSContext{
|
||||||
Proto: "udp",
|
Proto: "udp",
|
||||||
Req: req,
|
Req: req,
|
||||||
StartTime: time.Now(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolver *proxy.Proxy
|
var resolver *proxy.Proxy
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
@ -444,19 +445,10 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range upstreams {
|
err = validateUpstreamConfig(upstreams)
|
||||||
var ups string
|
if err != nil {
|
||||||
var domains []string
|
// Don't wrap the error since it's informative enough as is.
|
||||||
ups, domains, err = separateUpstream(u)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
// Don't wrap the error since it's informative enough as is.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = validateUpstream(ups, domains)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("validating upstream %q: %w", u, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conf, err = proxy.ParseUpstreamsConfig(
|
conf, err = proxy.ParseUpstreamsConfig(
|
||||||
@ -467,6 +459,7 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(conf.Upstreams) == 0 {
|
} else if len(conf.Upstreams) == 0 {
|
||||||
return nil, errors.Error("no default upstreams specified")
|
return nil, errors.Error("no default upstreams specified")
|
||||||
@ -475,6 +468,31 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
|
|||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateUpstreamConfig validates each upstream from the upstream
|
||||||
|
// configuration and returns an error if any upstream is invalid.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||||
|
func validateUpstreamConfig(conf []string) (err error) {
|
||||||
|
for _, u := range conf {
|
||||||
|
var ups []string
|
||||||
|
var domains []string
|
||||||
|
ups, domains, err = separateUpstream(u)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range ups {
|
||||||
|
_, err = validateUpstream(addr, domains)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("validating upstream %q: %w", addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateUpstreams validates each upstream and returns an error if any
|
// ValidateUpstreams validates each upstream and returns an error if any
|
||||||
// upstream is invalid or if there are no default upstreams specified.
|
// upstream is invalid or if there are no default upstreams specified.
|
||||||
//
|
//
|
||||||
@ -567,12 +585,12 @@ func validateUpstream(u string, domains []string) (useDefault bool, err error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// separateUpstream returns the upstream and the specified domains. domains is
|
// separateUpstream returns the upstreams and the specified domains. domains
|
||||||
// nil when the upstream is not domains-specific. Otherwise it may also be
|
// is nil when the upstream is not domains-specific. Otherwise it may also be
|
||||||
// empty.
|
// empty.
|
||||||
func separateUpstream(upstreamStr string) (ups string, domains []string, err error) {
|
func separateUpstream(upstreamStr string) (upstreams, domains []string, err error) {
|
||||||
if !strings.HasPrefix(upstreamStr, "[/") {
|
if !strings.HasPrefix(upstreamStr, "[/") {
|
||||||
return upstreamStr, nil, nil
|
return []string{upstreamStr}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }()
|
defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }()
|
||||||
@ -582,9 +600,9 @@ func separateUpstream(upstreamStr string) (ups string, domains []string, err err
|
|||||||
case 2:
|
case 2:
|
||||||
// Go on.
|
// Go on.
|
||||||
case 1:
|
case 1:
|
||||||
return "", nil, errors.Error("missing separator")
|
return nil, nil, errors.Error("missing separator")
|
||||||
default:
|
default:
|
||||||
return "", []string{}, errors.Error("duplicated separator")
|
return nil, nil, errors.Error("duplicated separator")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, host := range strings.Split(parts[0], "/") {
|
for i, host := range strings.Split(parts[0], "/") {
|
||||||
@ -594,13 +612,13 @@ func separateUpstream(upstreamStr string) (ups string, domains []string, err err
|
|||||||
|
|
||||||
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
|
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", domains, fmt.Errorf("domain at index %d: %w", i, err)
|
return nil, nil, fmt.Errorf("domain at index %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
domains = append(domains, host)
|
domains = append(domains, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts[1], domains, nil
|
return strings.Fields(parts[1]), domains, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// healthCheckFunc is a signature of function to check if upstream exchanges
|
// healthCheckFunc is a signature of function to check if upstream exchanges
|
||||||
@ -683,30 +701,73 @@ func (err domainSpecificTestError) Error() (msg string) {
|
|||||||
return fmt.Sprintf("WARNING: %s", err.error)
|
return fmt.Sprintf("WARNING: %s", err.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseUpstreamLine parses line and creates the [upstream.Upstream] using opts
|
// checkDNS parses line, creates DNS upstreams using opts, and checks if the
|
||||||
// and information from [s.dnsFilter.EtcHosts]. It returns an error if the line
|
// upstreams are exchanging correctly. It saves the result into a sync.Map
|
||||||
// is not a valid upstream line, see [upstream.AddressToUpstream]. It's a
|
// where key is an upstream address and value is "OK", if the upstream
|
||||||
// caller's responsibility to close u.
|
// exchanges correctly, or text of the error. It is intended to be used as a
|
||||||
func (s *Server) parseUpstreamLine(
|
// goroutine.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Separate to a different structure/file.
|
||||||
|
func (s *Server) checkDNS(
|
||||||
line string,
|
line string,
|
||||||
opts *upstream.Options,
|
opts *upstream.Options,
|
||||||
) (u upstream.Upstream, specific bool, err error) {
|
check healthCheckFunc,
|
||||||
// Separate upstream from domains list.
|
wg *sync.WaitGroup,
|
||||||
upstreamAddr, domains, err := separateUpstream(line)
|
m *sync.Map,
|
||||||
|
) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer log.OnPanic("dnsforward: checking upstreams")
|
||||||
|
|
||||||
|
upstreams, domains, err := separateUpstream(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("wrong upstream format: %w", err)
|
err = fmt.Errorf("wrong upstream format: %w", err)
|
||||||
|
m.Store(line, err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
specific = len(domains) > 0
|
specific := len(domains) > 0
|
||||||
|
|
||||||
useDefault, err := validateUpstream(upstreamAddr, domains)
|
for _, upstreamAddr := range upstreams {
|
||||||
if err != nil {
|
var useDefault bool
|
||||||
return nil, specific, fmt.Errorf("wrong upstream format: %w", err)
|
useDefault, err = validateUpstream(upstreamAddr, domains)
|
||||||
} else if useDefault {
|
if err != nil {
|
||||||
return nil, specific, nil
|
err = fmt.Errorf("wrong upstream format: %w", err)
|
||||||
|
m.Store(upstreamAddr, err.Error())
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if useDefault {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
|
||||||
|
|
||||||
|
err = s.checkUpstreamAddr(upstreamAddr, specific, opts, check)
|
||||||
|
if err != nil {
|
||||||
|
m.Store(upstreamAddr, err.Error())
|
||||||
|
} else {
|
||||||
|
m.Store(upstreamAddr, "OK")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
|
// checkUpstreamAddr creates the DNS upstream using opts and information from
|
||||||
|
// [s.dnsFilter.EtcHosts]. Checks if the DNS upstream exchanges correctly. It
|
||||||
|
// returns an error if addr is not valid DNS upstream address or the upstream
|
||||||
|
// is not exchanging correctly.
|
||||||
|
func (s *Server) checkUpstreamAddr(
|
||||||
|
addr string,
|
||||||
|
specific bool,
|
||||||
|
opts *upstream.Options,
|
||||||
|
check healthCheckFunc,
|
||||||
|
) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil && specific {
|
||||||
|
err = domainSpecificTestError{error: err}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
opts = &upstream.Options{
|
opts = &upstream.Options{
|
||||||
Bootstrap: opts.Bootstrap,
|
Bootstrap: opts.Bootstrap,
|
||||||
@ -716,42 +777,25 @@ func (s *Server) parseUpstreamLine(
|
|||||||
|
|
||||||
// dnsFilter can be nil during application update.
|
// dnsFilter can be nil during application update.
|
||||||
if s.dnsFilter != nil {
|
if s.dnsFilter != nil {
|
||||||
recs := s.dnsFilter.EtcHostsRecords(extractUpstreamHost(upstreamAddr))
|
recs := s.dnsFilter.EtcHostsRecords(extractUpstreamHost(addr))
|
||||||
for _, rec := range recs {
|
for _, rec := range recs {
|
||||||
opts.ServerIPAddrs = append(opts.ServerIPAddrs, rec.Addr.AsSlice())
|
opts.ServerIPAddrs = append(opts.ServerIPAddrs, rec.Addr.AsSlice())
|
||||||
}
|
}
|
||||||
sortNetIPAddrs(opts.ServerIPAddrs, opts.PreferIPv6)
|
sortNetIPAddrs(opts.ServerIPAddrs, opts.PreferIPv6)
|
||||||
}
|
}
|
||||||
u, err = upstream.AddressToUpstream(upstreamAddr, opts)
|
|
||||||
|
u, err := upstream.AddressToUpstream(addr, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, specific, fmt.Errorf("creating upstream for %q: %w", upstreamAddr, err)
|
return fmt.Errorf("creating upstream for %q: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, specific, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) checkDNS(line string, opts *upstream.Options, check healthCheckFunc) (err error) {
|
|
||||||
if IsCommentOrEmpty(line) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var u upstream.Upstream
|
|
||||||
var specific bool
|
|
||||||
defer func() {
|
|
||||||
if err != nil && specific {
|
|
||||||
err = domainSpecificTestError{error: err}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
u, specific, err = s.parseUpstreamLine(line, opts)
|
|
||||||
if err != nil || u == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { err = errors.WithDeferred(err, u.Close()) }()
|
defer func() { err = errors.WithDeferred(err, u.Close()) }()
|
||||||
|
|
||||||
return check(u)
|
return check(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleTestUpstreamDNS handles requests to the POST /control/test_upstream_dns
|
||||||
|
// endpoint.
|
||||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||||
req := &upstreamJSON{}
|
req := &upstreamJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(req)
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
@ -761,6 +805,10 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
|
||||||
|
req.FallbackDNS = stringutil.FilterOut(req.FallbackDNS, IsCommentOrEmpty)
|
||||||
|
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
|
||||||
|
|
||||||
opts := &upstream.Options{
|
opts := &upstream.Options{
|
||||||
Bootstrap: req.BootstrapDNS,
|
Bootstrap: req.BootstrapDNS,
|
||||||
Timeout: s.conf.UpstreamTimeout,
|
Timeout: s.conf.UpstreamTimeout,
|
||||||
@ -770,54 +818,34 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
|||||||
opts.Bootstrap = defaultBootstrap
|
opts.Bootstrap = defaultBootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
type upsCheckResult = struct {
|
wg := &sync.WaitGroup{}
|
||||||
err error
|
m := &sync.Map{}
|
||||||
host string
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
|
wg.Add(len(req.Upstreams) + len(req.FallbackDNS) + len(req.PrivateUpstreams))
|
||||||
req.FallbackDNS = stringutil.FilterOut(req.FallbackDNS, IsCommentOrEmpty)
|
|
||||||
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
|
|
||||||
|
|
||||||
upsNum := len(req.Upstreams) + len(req.FallbackDNS) + len(req.PrivateUpstreams)
|
|
||||||
result := make(map[string]string, upsNum)
|
|
||||||
resCh := make(chan upsCheckResult, upsNum)
|
|
||||||
|
|
||||||
for _, ups := range req.Upstreams {
|
for _, ups := range req.Upstreams {
|
||||||
go func(ups string) {
|
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
|
||||||
resCh <- upsCheckResult{
|
|
||||||
host: ups,
|
|
||||||
err: s.checkDNS(ups, opts, checkDNSUpstreamExc),
|
|
||||||
}
|
|
||||||
}(ups)
|
|
||||||
}
|
}
|
||||||
for _, ups := range req.FallbackDNS {
|
for _, ups := range req.FallbackDNS {
|
||||||
go func(ups string) {
|
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
|
||||||
resCh <- upsCheckResult{
|
|
||||||
host: ups,
|
|
||||||
err: s.checkDNS(ups, opts, checkDNSUpstreamExc),
|
|
||||||
}
|
|
||||||
}(ups)
|
|
||||||
}
|
}
|
||||||
for _, ups := range req.PrivateUpstreams {
|
for _, ups := range req.PrivateUpstreams {
|
||||||
go func(ups string) {
|
go s.checkDNS(ups, opts, checkPrivateUpstreamExc, wg, m)
|
||||||
resCh <- upsCheckResult{
|
|
||||||
host: ups,
|
|
||||||
err: s.checkDNS(ups, opts, checkPrivateUpstreamExc),
|
|
||||||
}
|
|
||||||
}(ups)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < upsNum; i++ {
|
wg.Wait()
|
||||||
|
|
||||||
|
result := map[string]string{}
|
||||||
|
m.Range(func(k, v any) bool {
|
||||||
// TODO(e.burkov): The upstreams used for both common and private
|
// TODO(e.burkov): The upstreams used for both common and private
|
||||||
// resolving should be reported separately.
|
// resolving should be reported separately.
|
||||||
pair := <-resCh
|
ups := k.(string)
|
||||||
if pair.err != nil {
|
status := v.(string)
|
||||||
result[pair.host] = pair.err.Error()
|
|
||||||
} else {
|
result[ups] = status
|
||||||
result[pair.host] = "OK"
|
|
||||||
}
|
return true
|
||||||
}
|
})
|
||||||
|
|
||||||
aghhttp.WriteJSONResponseOK(w, r, result)
|
aghhttp.WriteJSONResponseOK(w, r, result)
|
||||||
}
|
}
|
||||||
|
@ -49,13 +49,18 @@ func loadTestData(t *testing.T, casesFileName string, cases any) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExt = ".json"
|
const (
|
||||||
|
jsonExt = ".json"
|
||||||
|
|
||||||
|
// testBlockedRespTTL is the TTL for blocked responses to use in tests.
|
||||||
|
testBlockedRespTTL = 10
|
||||||
|
)
|
||||||
|
|
||||||
func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
ProtectionEnabled: true,
|
ProtectionEnabled: true,
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
BlockedResponseTTL: 10,
|
BlockedResponseTTL: testBlockedRespTTL,
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
SafeBrowsingCacheSize: 1000,
|
SafeBrowsingCacheSize: 1000,
|
||||||
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||||
@ -133,7 +138,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
ProtectionEnabled: true,
|
ProtectionEnabled: true,
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
BlockedResponseTTL: 10,
|
BlockedResponseTTL: testBlockedRespTTL,
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
SafeBrowsingCacheSize: 1000,
|
SafeBrowsingCacheSize: 1000,
|
||||||
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||||
@ -229,6 +234,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "blocked_response_ttl",
|
name: "blocked_response_ttl",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
|
}, {
|
||||||
|
name: "multiple_domain_specific_upstreams",
|
||||||
|
wantSet: "",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
var data map[string]struct {
|
var data map[string]struct {
|
||||||
@ -250,6 +258,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
s.dnsFilter.SetBlockingMode(filtering.BlockingModeDefault, netip.Addr{}, netip.Addr{})
|
s.dnsFilter.SetBlockingMode(filtering.BlockingModeDefault, netip.Addr{}, netip.Addr{})
|
||||||
s.conf = defaultConf
|
s.conf = defaultConf
|
||||||
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{}
|
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{}
|
||||||
|
s.dnsFilter.SetBlockedResponseTTL(testBlockedRespTTL)
|
||||||
})
|
})
|
||||||
|
|
||||||
rBody := io.NopCloser(bytes.NewReader(caseData.Req))
|
rBody := io.NopCloser(bytes.NewReader(caseData.Req))
|
||||||
@ -470,6 +479,8 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||||||
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
|
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
|
||||||
}).String()
|
}).String()
|
||||||
|
|
||||||
|
goodAndBadUps := strings.Join([]string{goodUps, badUps}, " ")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
upsTimeout = 100 * time.Millisecond
|
upsTimeout = 100 * time.Millisecond
|
||||||
|
|
||||||
@ -547,7 +558,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||||||
"upstream_dns": []string{"[/domain.example/]" + badUps},
|
"upstream_dns": []string{"[/domain.example/]" + badUps},
|
||||||
},
|
},
|
||||||
wantResp: map[string]any{
|
wantResp: map[string]any{
|
||||||
"[/domain.example/]" + badUps: `WARNING: couldn't communicate ` +
|
badUps: `WARNING: couldn't communicate ` +
|
||||||
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||||
`dns: id mismatch`,
|
`dns: id mismatch`,
|
||||||
},
|
},
|
||||||
@ -585,6 +596,40 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||||||
goodUps: "OK",
|
goodUps: "OK",
|
||||||
},
|
},
|
||||||
name: "fallback_comment_mix",
|
name: "fallback_comment_mix",
|
||||||
|
}, {
|
||||||
|
body: map[string]any{
|
||||||
|
"upstream_dns": []string{"[/domain.example/]" + goodUps + " " + badUps},
|
||||||
|
},
|
||||||
|
wantResp: map[string]any{
|
||||||
|
goodUps: "OK",
|
||||||
|
badUps: `WARNING: couldn't communicate ` +
|
||||||
|
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||||
|
`dns: id mismatch`,
|
||||||
|
},
|
||||||
|
name: "multiple_domain_specific_upstreams",
|
||||||
|
}, {
|
||||||
|
body: map[string]any{
|
||||||
|
"upstream_dns": []string{"[/domain.example/]/]1.2.3.4"},
|
||||||
|
},
|
||||||
|
wantResp: map[string]any{
|
||||||
|
"[/domain.example/]/]1.2.3.4": `wrong upstream format: ` +
|
||||||
|
`bad upstream for domain "[/domain.example/]/]1.2.3.4": ` +
|
||||||
|
`duplicated separator`,
|
||||||
|
},
|
||||||
|
name: "bad_specification",
|
||||||
|
}, {
|
||||||
|
body: map[string]any{
|
||||||
|
"upstream_dns": []string{"[/domain.example/]" + goodAndBadUps},
|
||||||
|
"fallback_dns": []string{"[/domain.example/]" + goodAndBadUps},
|
||||||
|
"private_upstream": []string{"[/domain.example/]" + goodAndBadUps},
|
||||||
|
},
|
||||||
|
wantResp: map[string]any{
|
||||||
|
goodUps: "OK",
|
||||||
|
badUps: `WARNING: couldn't communicate ` +
|
||||||
|
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||||
|
`dns: id mismatch`,
|
||||||
|
},
|
||||||
|
name: "all_different",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -2,7 +2,6 @@ package dnsforward
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
@ -270,10 +269,9 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
|||||||
replReq.RecursionDesired = true
|
replReq.RecursionDesired = true
|
||||||
|
|
||||||
newContext := &proxy.DNSContext{
|
newContext := &proxy.DNSContext{
|
||||||
Proto: d.Proto,
|
Proto: d.Proto,
|
||||||
Addr: d.Addr,
|
Addr: d.Addr,
|
||||||
StartTime: time.Now(),
|
Req: &replReq,
|
||||||
Req: &replReq,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prx := s.proxy()
|
prx := s.proxy()
|
||||||
|
@ -20,11 +20,10 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
|||||||
log.Debug("dnsforward: started processing querylog and stats")
|
log.Debug("dnsforward: started processing querylog and stats")
|
||||||
defer log.Debug("dnsforward: finished processing querylog and stats")
|
defer log.Debug("dnsforward: finished processing querylog and stats")
|
||||||
|
|
||||||
elapsed := time.Since(dctx.startTime)
|
|
||||||
pctx := dctx.proxyCtx
|
pctx := dctx.proxyCtx
|
||||||
|
|
||||||
q := pctx.Req.Question[0]
|
q := pctx.Req.Question[0]
|
||||||
host := aghnet.NormalizeDomain(q.Name)
|
host := aghnet.NormalizeDomain(q.Name)
|
||||||
|
processingTime := time.Since(dctx.startTime)
|
||||||
|
|
||||||
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
||||||
ip = slices.Clone(ip)
|
ip = slices.Clone(ip)
|
||||||
@ -43,7 +42,7 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
|||||||
defer s.serverLock.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
if s.shouldLog(host, qt, cl, ids) {
|
if s.shouldLog(host, qt, cl, ids) {
|
||||||
s.logQuery(dctx, pctx, elapsed, ip)
|
s.logQuery(dctx, ip, processingTime)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(
|
log.Debug(
|
||||||
"dnsforward: request %s %s %q from %s ignored; not adding to querylog",
|
"dnsforward: request %s %s %q from %s ignored; not adding to querylog",
|
||||||
@ -55,7 +54,7 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.shouldCountStat(host, qt, cl, ids) {
|
if s.shouldCountStat(host, qt, cl, ids) {
|
||||||
s.updateStats(dctx, elapsed, *dctx.result, ipStr)
|
s.updateStats(dctx, ipStr, processingTime)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(
|
log.Debug(
|
||||||
"dnsforward: request %s %s %q from %s ignored; not counting in stats",
|
"dnsforward: request %s %s %q from %s ignored; not counting in stats",
|
||||||
@ -90,12 +89,9 @@ func (s *Server) shouldCountStat(host string, qt, cl uint16, ids []string) (ok b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// logQuery pushes the request details into the query log.
|
// logQuery pushes the request details into the query log.
|
||||||
func (s *Server) logQuery(
|
func (s *Server) logQuery(dctx *dnsContext, ip net.IP, processingTime time.Duration) {
|
||||||
dctx *dnsContext,
|
pctx := dctx.proxyCtx
|
||||||
pctx *proxy.DNSContext,
|
|
||||||
elapsed time.Duration,
|
|
||||||
ip net.IP,
|
|
||||||
) {
|
|
||||||
p := &querylog.AddParams{
|
p := &querylog.AddParams{
|
||||||
Question: pctx.Req,
|
Question: pctx.Req,
|
||||||
ReqECS: pctx.ReqECS,
|
ReqECS: pctx.ReqECS,
|
||||||
@ -104,7 +100,7 @@ func (s *Server) logQuery(
|
|||||||
Result: dctx.result,
|
Result: dctx.result,
|
||||||
ClientID: dctx.clientID,
|
ClientID: dctx.clientID,
|
||||||
ClientIP: ip,
|
ClientIP: ip,
|
||||||
Elapsed: elapsed,
|
Elapsed: processingTime,
|
||||||
AuthenticatedData: dctx.responseAD,
|
AuthenticatedData: dctx.responseAD,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,30 +128,27 @@ func (s *Server) logQuery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updatesStats writes the request into statistics.
|
// updatesStats writes the request into statistics.
|
||||||
func (s *Server) updateStats(
|
func (s *Server) updateStats(dctx *dnsContext, clientIP string, processingTime time.Duration) {
|
||||||
ctx *dnsContext,
|
pctx := dctx.proxyCtx
|
||||||
elapsed time.Duration,
|
|
||||||
res filtering.Result,
|
|
||||||
clientIP string,
|
|
||||||
) {
|
|
||||||
pctx := ctx.proxyCtx
|
|
||||||
e := &stats.Entry{
|
e := &stats.Entry{
|
||||||
Domain: aghnet.NormalizeDomain(pctx.Req.Question[0].Name),
|
Domain: aghnet.NormalizeDomain(pctx.Req.Question[0].Name),
|
||||||
Result: stats.RNotFiltered,
|
Result: stats.RNotFiltered,
|
||||||
Time: elapsed,
|
ProcessingTime: processingTime,
|
||||||
|
UpstreamTime: pctx.QueryDuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
if pctx.Upstream != nil {
|
if pctx.Upstream != nil {
|
||||||
e.Upstream = pctx.Upstream.Address()
|
e.Upstream = pctx.Upstream.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientID := ctx.clientID; clientID != "" {
|
if clientID := dctx.clientID; clientID != "" {
|
||||||
e.Client = clientID
|
e.Client = clientID
|
||||||
} else {
|
} else {
|
||||||
e.Client = clientIP
|
e.Client = clientIP
|
||||||
}
|
}
|
||||||
|
|
||||||
switch res.Reason {
|
switch dctx.result.Reason {
|
||||||
case filtering.FilteredSafeBrowsing:
|
case filtering.FilteredSafeBrowsing:
|
||||||
e.Result = stats.RSafeBrowsing
|
e.Result = stats.RSafeBrowsing
|
||||||
case filtering.FilteredParental:
|
case filtering.FilteredParental:
|
||||||
|
@ -839,5 +839,47 @@
|
|||||||
"edns_cs_use_custom": false,
|
"edns_cs_use_custom": false,
|
||||||
"edns_cs_custom_ip": ""
|
"edns_cs_custom_ip": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"multiple_domain_specific_upstreams": {
|
||||||
|
"req": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:77",
|
||||||
|
"[/example.com/]8.8.4.4:77 9.9.9.10 https://1.1.1.1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"want": {
|
||||||
|
"upstream_dns": [
|
||||||
|
"8.8.8.8:77",
|
||||||
|
"[/example.com/]8.8.4.4:77 9.9.9.10 https://1.1.1.1"
|
||||||
|
],
|
||||||
|
"upstream_dns_file": "",
|
||||||
|
"bootstrap_dns": [
|
||||||
|
"9.9.9.10",
|
||||||
|
"149.112.112.10",
|
||||||
|
"2620:fe::10",
|
||||||
|
"2620:fe::fe:10"
|
||||||
|
],
|
||||||
|
"fallback_dns": [],
|
||||||
|
"protection_enabled": true,
|
||||||
|
"protection_disabled_until": null,
|
||||||
|
"ratelimit": 0,
|
||||||
|
"blocking_mode": "default",
|
||||||
|
"blocking_ipv4": "",
|
||||||
|
"blocking_ipv6": "",
|
||||||
|
"blocked_response_ttl": 10,
|
||||||
|
"edns_cs_enabled": false,
|
||||||
|
"dnssec_enabled": false,
|
||||||
|
"disable_ipv6": false,
|
||||||
|
"upstream_mode": "",
|
||||||
|
"cache_size": 0,
|
||||||
|
"cache_ttl_min": 0,
|
||||||
|
"cache_ttl_max": 0,
|
||||||
|
"cache_optimistic": false,
|
||||||
|
"resolve_clients": false,
|
||||||
|
"use_private_ptr_resolvers": false,
|
||||||
|
"local_ptr_upstreams": [],
|
||||||
|
"edns_cs_use_custom": false,
|
||||||
|
"edns_cs_custom_ip": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,30 +263,6 @@ func assignUniqueFilterID() int64 {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up a timer that will be checking for filters updates periodically
|
|
||||||
func (d *DNSFilter) periodicallyRefreshFilters() {
|
|
||||||
const maxInterval = 1 * 60 * 60
|
|
||||||
ivl := 5 // use a dynamically increasing time interval
|
|
||||||
for {
|
|
||||||
isNetErr, ok := false, false
|
|
||||||
if d.conf.FiltersUpdateIntervalHours != 0 {
|
|
||||||
_, isNetErr, ok = d.tryRefreshFilters(true, true, false)
|
|
||||||
if ok && !isNetErr {
|
|
||||||
ivl = maxInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNetErr {
|
|
||||||
ivl *= 2
|
|
||||||
if ivl > maxInterval {
|
|
||||||
ivl = maxInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(ivl) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryRefreshFilters is like [refreshFilters], but backs down if the update is
|
// tryRefreshFilters is like [refreshFilters], but backs down if the update is
|
||||||
// already going on.
|
// already going on.
|
||||||
//
|
//
|
||||||
|
@ -257,6 +257,9 @@ type DNSFilter struct {
|
|||||||
// conf contains filtering parameters.
|
// conf contains filtering parameters.
|
||||||
conf *Config
|
conf *Config
|
||||||
|
|
||||||
|
// done is the channel to signal to stop running filters updates loop.
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
// Channel for passing data to filters-initializer goroutine
|
// Channel for passing data to filters-initializer goroutine
|
||||||
filtersInitializerChan chan filtersInitializerParams
|
filtersInitializerChan chan filtersInitializerParams
|
||||||
filtersInitializerLock sync.Mutex
|
filtersInitializerLock sync.Mutex
|
||||||
@ -424,24 +427,15 @@ func (d *DNSFilter) setFilters(blockFilters, allowFilters []Filter, async bool)
|
|||||||
return d.initFiltering(allowFilters, blockFilters)
|
return d.initFiltering(allowFilters, blockFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts initializing new filters by signal from channel
|
|
||||||
func (d *DNSFilter) filtersInitializer() {
|
|
||||||
for {
|
|
||||||
params := <-d.filtersInitializerChan
|
|
||||||
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("filtering: initializing: %s", err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close - close the object
|
// Close - close the object
|
||||||
func (d *DNSFilter) Close() {
|
func (d *DNSFilter) Close() {
|
||||||
d.engineLock.Lock()
|
d.engineLock.Lock()
|
||||||
defer d.engineLock.Unlock()
|
defer d.engineLock.Unlock()
|
||||||
|
|
||||||
|
if d.done != nil {
|
||||||
|
d.done <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
d.reset()
|
d.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1131,19 +1125,64 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start - start the module:
|
// Start registers web handlers and starts filters updates loop.
|
||||||
// . start async filtering initializer goroutine
|
|
||||||
// . register web handlers
|
|
||||||
func (d *DNSFilter) Start() {
|
func (d *DNSFilter) Start() {
|
||||||
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
||||||
go d.filtersInitializer()
|
d.done = make(chan struct{}, 1)
|
||||||
|
|
||||||
d.RegisterFilteringHandlers()
|
d.RegisterFilteringHandlers()
|
||||||
|
|
||||||
// Here we should start updating filters,
|
go d.updatesLoop()
|
||||||
// but currently we can't wake up the periodic task to do so.
|
}
|
||||||
// So for now we just start this periodic task from here.
|
|
||||||
go d.periodicallyRefreshFilters()
|
// updatesLoop initializes new filters and checks for filters updates in a loop.
|
||||||
|
func (d *DNSFilter) updatesLoop() {
|
||||||
|
defer log.OnPanic("filtering: updates loop")
|
||||||
|
|
||||||
|
ivl := time.Second * 5
|
||||||
|
t := time.NewTimer(ivl)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case params := <-d.filtersInitializerChan:
|
||||||
|
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("filtering: initializing: %s", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case <-t.C:
|
||||||
|
ivl = d.periodicallyRefreshFilters(ivl)
|
||||||
|
t.Reset(ivl)
|
||||||
|
case <-d.done:
|
||||||
|
t.Stop()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// periodicallyRefreshFilters checks for filters updates and returns time
|
||||||
|
// interval for the next update.
|
||||||
|
func (d *DNSFilter) periodicallyRefreshFilters(ivl time.Duration) (nextIvl time.Duration) {
|
||||||
|
const maxInterval = time.Hour
|
||||||
|
|
||||||
|
if d.conf.FiltersUpdateIntervalHours == 0 {
|
||||||
|
return ivl
|
||||||
|
}
|
||||||
|
|
||||||
|
isNetErr, ok := false, false
|
||||||
|
_, isNetErr, ok = d.tryRefreshFilters(true, true, false)
|
||||||
|
|
||||||
|
if ok && !isNetErr {
|
||||||
|
ivl = maxInterval
|
||||||
|
} else if isNetErr {
|
||||||
|
ivl *= 2
|
||||||
|
// TODO(s.chzhen): Use built-in function max in Go 1.21.
|
||||||
|
ivl = mathutil.Max(ivl, maxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ivl
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe browsing and parental control methods.
|
// Safe browsing and parental control methods.
|
||||||
|
@ -428,6 +428,15 @@ var blockedServices = []blockedService{{
|
|||||||
"||bnet.163.com^",
|
"||bnet.163.com^",
|
||||||
"||bnet.cn^",
|
"||bnet.cn^",
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
ID: "canais_globo",
|
||||||
|
Name: "Canais Globo",
|
||||||
|
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 980 980\"><path d=\"M455.5 1.1a484.3 484.3 0 0 0-258 95.4 501.4 501.4 0 0 0-101.1 101A483.8 483.8 0 0 0 4 426.5 491.7 491.7 0 0 0 54.7 716a481.2 481.2 0 0 0 89.7 121.5C252.7 945.3 400 995.1 554 975.9c92.4-11.4 178-49.3 253.5-112 15-12.4 47.5-45.5 60.6-61.7A483.7 483.7 0 0 0 976 553.5a488.4 488.4 0 0 0-135.7-406.6A494.8 494.8 0 0 0 640.8 23.2 506.9 506.9 0 0 0 455.5 1.1zm-76.4 245.4c6.4 2.3 359.1 210.1 364.3 214.7 2.8 2.4 5.8 6.5 7.8 10.6 3.2 6.4 3.3 7.2 3.3 18.2s-.1 11.8-3.3 18.2c-2 4.1-5 8.2-7.8 10.6-6.7 5.9-358.7 212.7-365.3 214.6a42 42 0 0 1-29.1-2.6 46 46 0 0 1-18.6-19l-2.9-6.3v-431l2.9-6.2c2.7-6 9.5-13.6 15.7-17.6a44.3 44.3 0 0 1 33-4.2z\"/></svg>"),
|
||||||
|
Rules: []string{
|
||||||
|
"||canaisglobo.globo.com^",
|
||||||
|
"||globosat.globo.com^",
|
||||||
|
"||gsatmulti.globo.com^",
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
ID: "claro",
|
ID: "claro",
|
||||||
Name: "Claro",
|
Name: "Claro",
|
||||||
@ -1672,6 +1681,22 @@ var blockedServices = []blockedService{{
|
|||||||
"||linkedin.qtlcdn.com^",
|
"||linkedin.qtlcdn.com^",
|
||||||
"||lnkd.in^",
|
"||lnkd.in^",
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
ID: "lionsgateplus",
|
||||||
|
Name: "Lionsgate+",
|
||||||
|
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"-21 2 120 120\"><path d=\"M35 3.7v84.8h43.9v31.8H0V3.7Z\"/></svg>"),
|
||||||
|
Rules: []string{
|
||||||
|
"||lionsgateplus.com^",
|
||||||
|
"||starz.com^",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ID: "looke",
|
||||||
|
Name: "Looke",
|
||||||
|
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 -28 100 100\"><path d=\"m16.1.1-2-.1C7 0 0 5.2 0 11.2 0 15.4 2.4 17 6.2 17 5 16 4 14.8 4 11.4 4 7 6.7 4 11 2.8v33.5h1c6.1 0 10.5 7.5 15.5 7.5 3.3 0 5.3-2.3 5.3-5 0-.4 0-.8-.2-1.2a9 9 0 0 1-3.6 1c-5.4 0-8.8-3.4-12.9-4.3V.3z\"/><path d=\"M31.6 11.2c-7.1 0-9.3 7.7-9.3 13.2 0 8.2 4.7 11.1 9 11.1 5 0 8-4.7 8-12.5v-2c3-.4 6-1.8 7.3-3.9L46 16a9.2 9.2 0 0 1-6.5 2.8H39c-.6-4.2-2.5-7.5-7.5-7.5zm.5 20.7c-2.1 0-4.6-1.5-4.6-7.7 0-4.6 1.4-10.4 5.4-10.4 1.4 0 2.6.8 3.4 3-1.2 0-2 .5-2 2 0 1.6.9 2.3 2.7 2.4v1.1c0 6-1.6 9.6-4.9 9.6z\"/><path d=\"M51.6 11.2c-7.1 0-9.3 7.7-9.3 13.2 0 8.2 4.7 11.1 9 11.1 5 0 8-4.7 8-12.5v-2c3-.4 6-1.8 7.3-3.9L66 16a9.2 9.2 0 0 1-6.5 2.8H59c-.6-4.2-2.5-7.5-7.5-7.5zm.5 20.7c-2.1 0-4.6-1.5-4.6-7.7 0-4.6 1.4-10.4 5.4-10.4 1.4 0 2.6.8 3.4 3-1.2 0-2 .5-2 2 0 1.6.9 2.3 2.7 2.4v1.1c0 6-1.6 9.6-4.9 9.6z\"/><path d=\"M63 2.6v32.6h4.7v-10c1-2.8 2.2-3.7 3.9-3.7 1.6 0 3 .8 3 4.2V30c0 3 1.5 5.4 5.3 5.4 2 0 5.2-.6 6.6-8.8h-1.7c-.8 3.6-2 5.2-3.6 5.2-1.7 0-1.9-1.6-1.9-2.5l.2-4.1c0-3.1-.8-7.4-6.5-7.4h-.5l8-6.5h-4.6l-8.2 7.8V1.9Z\"/><path d=\"M99.6 17.4c0-5-3.2-6.2-6.4-6.2-6.8 0-9 8-9 13.3 0 8 4.8 11 9 11 3.6 0 6.8-1.9 6.8-4.7l-.1-1.2c-1.2 1.7-3.1 2.3-5.2 2.3-2.7 0-5-1.1-5.5-6.2 6.5-.7 10.4-3.9 10.4-8.3zm-10.4 6.5c0-4.7 1.6-10.4 5-10.4 1.5 0 2.3 1 2.3 3.4 0 3.6-2.8 6.2-7.3 7z\"/></svg>"),
|
||||||
|
Rules: []string{
|
||||||
|
"||looke.com.br^",
|
||||||
|
"||ottvs.com.br^",
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
ID: "mail_ru",
|
ID: "mail_ru",
|
||||||
Name: "Mail.ru",
|
Name: "Mail.ru",
|
||||||
@ -2023,6 +2048,13 @@ var blockedServices = []blockedService{{
|
|||||||
Rules: []string{
|
Rules: []string{
|
||||||
"||pluto.tv^",
|
"||pluto.tv^",
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
ID: "privacy",
|
||||||
|
Name: "Privacy",
|
||||||
|
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"-2 0 42 42\"><path fill-rule=\"evenodd\" d=\"m28.516 30.648-.857-1.386-1.935-3.136a9.853 9.853 0 0 0 2.523-3.66 9.76 9.76 0 0 0 .31-6.26 9.853 9.853 0 0 0-3.562-5.185 9.955 9.955 0 0 0-5.985-2 9.94 9.94 0 0 0-5.986 2 9.848 9.848 0 0 0-3.564 5.185 9.76 9.76 0 0 0 .31 6.26 9.853 9.853 0 0 0 2.523 3.66L5.031 37.892a.654.654 0 0 1-.312.267.665.665 0 0 1-.42.013.65.65 0 0 1-.343-.225.65.65 0 0 1-.123-.397V18.875h-.007c0-3.331 1.107-6.468 3.022-9.016a15.152 15.152 0 0 1 7.842-5.436 15.292 15.292 0 0 1 9.572.306c3 1.096 5.65 3.126 7.481 5.92a14.998 14.998 0 0 1 2.427 9.197 15.002 15.002 0 0 1-3.587 8.808 15.267 15.267 0 0 1-2.065 1.992m-9.505 3.26c1.129 0 2.284-.079 3.388-.328.979-.222 1.936-.54 2.856-.951l-.854-1.383-2.838-4.6a1.888 1.888 0 0 1 .636-2.608 6.058 6.058 0 0 0 2.487-2.953 6.02 6.02 0 0 0-1.995-7.03 6.115 6.115 0 0 0-3.68-1.225 6.12 6.12 0 0 0-3.682 1.225 6.03 6.03 0 0 0-2.185 3.178 6.008 6.008 0 0 0 .19 3.852 6.056 6.056 0 0 0 2.487 2.953 1.889 1.889 0 0 1 .637 2.607l-1.845 2.99-1.562 2.532-.278.45a14.152 14.152 0 0 0 6.24 1.292h-.002ZM8.772 39.098l-.476.772a4.468 4.468 0 0 1-2.184 1.829 4.48 4.48 0 0 1-2.846.13 4.468 4.468 0 0 1-2.364-1.592A4.404 4.404 0 0 1 0 37.552V18.875h.007c0-4.183 1.382-8.113 3.771-11.29A18.992 18.992 0 0 1 13.611.78a19.102 19.102 0 0 1 11.967.38 18.97 18.97 0 0 1 9.368 7.422 18.758 18.758 0 0 1 3.04 11.501 18.767 18.767 0 0 1-4.5 11.024 19.006 19.006 0 0 1-10.247 6.17 19.07 19.07 0 0 1-3.613.463c-.089-.003-.534.007-.613.007a19.111 19.111 0 0 1-8.257-1.867l-1.984 3.215v.001Z\" clip-rule=\"evenodd\"/></svg>"),
|
||||||
|
Rules: []string{
|
||||||
|
"||privacy.com.br^",
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
ID: "qq",
|
ID: "qq",
|
||||||
Name: "QQ",
|
Name: "QQ",
|
||||||
@ -2298,11 +2330,15 @@ var blockedServices = []blockedService{{
|
|||||||
"||huoshanzhibo.com^",
|
"||huoshanzhibo.com^",
|
||||||
"||muscdn.com^",
|
"||muscdn.com^",
|
||||||
"||musical.ly^",
|
"||musical.ly^",
|
||||||
|
"||p16-tiktok-*.ibyteimg.com^",
|
||||||
"||pstatp.com^",
|
"||pstatp.com^",
|
||||||
"||snssdk.com^",
|
"||snssdk.com^",
|
||||||
"||tiktok.com^",
|
"||tiktok.com^",
|
||||||
|
"||tiktokcdn-us.com^",
|
||||||
"||tiktokcdn.com^",
|
"||tiktokcdn.com^",
|
||||||
"||tiktokv.com^",
|
"||tiktokv.com^",
|
||||||
|
"||ttlivecdn.com.c.bytefcdn-oversea.com^",
|
||||||
|
"||ttlivecdn.com^",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
ID: "tinder",
|
ID: "tinder",
|
||||||
|
@ -4,32 +4,17 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
|
||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cookieTTL is the time-to-live of the session cookie.
|
|
||||||
const cookieTTL = 365 * timeutil.Day
|
|
||||||
|
|
||||||
// sessionCookieName is the name of the session cookie.
|
|
||||||
const sessionCookieName = "agh_session"
|
|
||||||
|
|
||||||
// sessionTokenSize is the length of session token in bytes.
|
// sessionTokenSize is the length of session token in bytes.
|
||||||
const sessionTokenSize = 16
|
const sessionTokenSize = 16
|
||||||
|
|
||||||
@ -69,7 +54,7 @@ func (s *session) deserialize(data []byte) bool {
|
|||||||
// Auth - global object
|
// Auth - global object
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
db *bbolt.DB
|
db *bbolt.DB
|
||||||
raleLimiter *authRateLimiter
|
rateLimiter *authRateLimiter
|
||||||
sessions map[string]*session
|
sessions map[string]*session
|
||||||
users []webUser
|
users []webUser
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
@ -77,6 +62,8 @@ type Auth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// webUser represents a user of the Web UI.
|
// webUser represents a user of the Web UI.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Improve naming.
|
||||||
type webUser struct {
|
type webUser struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
PasswordHash string `yaml:"password"`
|
PasswordHash string `yaml:"password"`
|
||||||
@ -88,7 +75,7 @@ func InitAuth(dbFilename string, users []webUser, sessionTTL uint32, rateLimiter
|
|||||||
|
|
||||||
a := &Auth{
|
a := &Auth{
|
||||||
sessionTTL: sessionTTL,
|
sessionTTL: sessionTTL,
|
||||||
raleLimiter: rateLimiter,
|
rateLimiter: rateLimiter,
|
||||||
sessions: make(map[string]*session),
|
sessions: make(map[string]*session),
|
||||||
users: users,
|
users: users,
|
||||||
}
|
}
|
||||||
@ -216,8 +203,8 @@ func (a *Auth) storeSession(data []byte, s *session) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove session from file
|
// removeSessionFromFile removes a stored session from the DB file on disk.
|
||||||
func (a *Auth) removeSession(sess []byte) {
|
func (a *Auth) removeSessionFromFile(sess []byte) {
|
||||||
tx, err := a.db.Begin(true)
|
tx, err := a.db.Begin(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: bbolt.Begin: %s", err)
|
log.Error("auth: bbolt.Begin: %s", err)
|
||||||
@ -279,7 +266,7 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
|
|||||||
if s.expire <= now {
|
if s.expire <= now {
|
||||||
delete(a.sessions, sess)
|
delete(a.sessions, sess)
|
||||||
key, _ := hex.DecodeString(sess)
|
key, _ := hex.DecodeString(sess)
|
||||||
a.removeSession(key)
|
a.removeSessionFromFile(key)
|
||||||
|
|
||||||
return checkSessionExpired
|
return checkSessionExpired
|
||||||
}
|
}
|
||||||
@ -301,351 +288,17 @@ func (a *Auth) checkSession(sess string) (res checkSessionResult) {
|
|||||||
return checkSessionOK
|
return checkSessionOK
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSession - remove session
|
// removeSession removes the session from the active sessions and the disk.
|
||||||
func (a *Auth) RemoveSession(sess string) {
|
func (a *Auth) removeSession(sess string) {
|
||||||
key, _ := hex.DecodeString(sess)
|
key, _ := hex.DecodeString(sess)
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
delete(a.sessions, sess)
|
delete(a.sessions, sess)
|
||||||
a.lock.Unlock()
|
a.lock.Unlock()
|
||||||
a.removeSession(key)
|
a.removeSessionFromFile(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
type loginJSON struct {
|
// addUser adds a new user with the given password.
|
||||||
Name string `json:"name"`
|
func (a *Auth) addUser(u *webUser, password string) (err error) {
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSessionToken returns cryptographically secure randomly generated slice of
|
|
||||||
// bytes of sessionTokenSize length.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Think about using byte array instead of byte slice.
|
|
||||||
func newSessionToken() (data []byte, err error) {
|
|
||||||
randData := make([]byte, sessionTokenSize)
|
|
||||||
|
|
||||||
_, err = rand.Read(randData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return randData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCookie creates a new authentication cookie.
|
|
||||||
func (a *Auth) newCookie(req loginJSON, addr string) (c *http.Cookie, err error) {
|
|
||||||
rateLimiter := a.raleLimiter
|
|
||||||
u, ok := a.findUser(req.Name, req.Password)
|
|
||||||
if !ok {
|
|
||||||
if rateLimiter != nil {
|
|
||||||
rateLimiter.inc(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Error("invalid username or password")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rateLimiter != nil {
|
|
||||||
rateLimiter.remove(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
sess, err := newSessionToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("generating token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
a.addSession(sess, &session{
|
|
||||||
userName: u.Name,
|
|
||||||
expire: uint32(now.Unix()) + a.sessionTTL,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &http.Cookie{
|
|
||||||
Name: sessionCookieName,
|
|
||||||
Value: hex.EncodeToString(sess),
|
|
||||||
Path: "/",
|
|
||||||
Expires: now.Add(cookieTTL),
|
|
||||||
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// realIP extracts the real IP address of the client from an HTTP request using
|
|
||||||
// the known HTTP headers.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Currently, this is basically a copy of a similar function in
|
|
||||||
// module dnsproxy. This should really become a part of module golibs and be
|
|
||||||
// replaced both here and there. Or be replaced in both places by
|
|
||||||
// a well-maintained third-party module.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Support header Forwarded from RFC 7329.
|
|
||||||
func realIP(r *http.Request) (ip net.IP, err error) {
|
|
||||||
proxyHeaders := []string{
|
|
||||||
httphdr.CFConnectingIP,
|
|
||||||
httphdr.TrueClientIP,
|
|
||||||
httphdr.XRealIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, h := range proxyHeaders {
|
|
||||||
v := r.Header.Get(h)
|
|
||||||
ip = net.ParseIP(v)
|
|
||||||
if ip != nil {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the above yielded any results, get the leftmost IP address
|
|
||||||
// from the X-Forwarded-For header.
|
|
||||||
s := r.Header.Get(httphdr.XForwardedFor)
|
|
||||||
ipStrs := strings.SplitN(s, ", ", 2)
|
|
||||||
ip = net.ParseIP(ipStrs[0])
|
|
||||||
if ip != nil {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// When everything else fails, just return the remote address as understood
|
|
||||||
// by the stdlib.
|
|
||||||
ipStr, err := netutil.SplitHost(r.RemoteAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting ip from client addr: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.ParseIP(ipStr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeErrorWithIP is like [aghhttp.Error], but includes the remote IP address
|
|
||||||
// when it writes to the log.
|
|
||||||
func writeErrorWithIP(
|
|
||||||
r *http.Request,
|
|
||||||
w http.ResponseWriter,
|
|
||||||
code int,
|
|
||||||
remoteIP string,
|
|
||||||
format string,
|
|
||||||
args ...any,
|
|
||||||
) {
|
|
||||||
text := fmt.Sprintf(format, args...)
|
|
||||||
log.Error("%s %s %s: from ip %s: %s", r.Method, r.Host, r.URL, remoteIP, text)
|
|
||||||
http.Error(w, text, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := loginJSON{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var remoteIP string
|
|
||||||
// realIP cannot be used here without taking TrustedProxies into account due
|
|
||||||
// to security issues.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2799.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Use realIP when the issue will be fixed.
|
|
||||||
if remoteIP, err = netutil.SplitHost(r.RemoteAddr); err != nil {
|
|
||||||
writeErrorWithIP(
|
|
||||||
r,
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
r.RemoteAddr,
|
|
||||||
"auth: getting remote address: %s",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rateLimiter := Context.auth.raleLimiter; rateLimiter != nil {
|
|
||||||
if left := rateLimiter.check(remoteIP); left > 0 {
|
|
||||||
w.Header().Set(httphdr.RetryAfter, strconv.Itoa(int(left.Seconds())))
|
|
||||||
writeErrorWithIP(
|
|
||||||
r,
|
|
||||||
w,
|
|
||||||
http.StatusTooManyRequests,
|
|
||||||
remoteIP,
|
|
||||||
"auth: blocked for %s",
|
|
||||||
left,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie, err := Context.auth.newCookie(req, remoteIP)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorWithIP(r, w, http.StatusForbidden, remoteIP, "%s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use realIP here, since this IP address is only used for logging.
|
|
||||||
ip, err := realIP(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: getting real ip from request with remote ip %s: %s", remoteIP, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("auth: user %q successfully logged in from ip %v", req.Name, ip)
|
|
||||||
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
|
|
||||||
h := w.Header()
|
|
||||||
h.Set(httphdr.CacheControl, "no-store, no-cache, must-revalidate, proxy-revalidate")
|
|
||||||
h.Set(httphdr.Pragma, "no-cache")
|
|
||||||
h.Set(httphdr.Expires, "0")
|
|
||||||
|
|
||||||
aghhttp.OK(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
respHdr := w.Header()
|
|
||||||
c, err := r.Cookie(sessionCookieName)
|
|
||||||
if err != nil {
|
|
||||||
// The only error that is returned from r.Cookie is [http.ErrNoCookie].
|
|
||||||
// The user is already logged out.
|
|
||||||
respHdr.Set(httphdr.Location, "/login.html")
|
|
||||||
w.WriteHeader(http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Context.auth.RemoveSession(c.Value)
|
|
||||||
|
|
||||||
c = &http.Cookie{
|
|
||||||
Name: sessionCookieName,
|
|
||||||
Value: "",
|
|
||||||
Path: "/",
|
|
||||||
Expires: time.Unix(0, 0),
|
|
||||||
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
respHdr.Set(httphdr.Location, "/login.html")
|
|
||||||
respHdr.Set(httphdr.SetCookie, c.String())
|
|
||||||
w.WriteHeader(http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterAuthHandlers - register handlers
|
|
||||||
func RegisterAuthHandlers() {
|
|
||||||
Context.mux.Handle("/control/login", postInstallHandler(ensureHandler(http.MethodPost, handleLogin)))
|
|
||||||
httpRegister(http.MethodGet, "/control/logout", handleLogout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// optionalAuthThird return true if user should authenticate first.
|
|
||||||
func optionalAuthThird(w http.ResponseWriter, r *http.Request) (mustAuth bool) {
|
|
||||||
if glProcessCookie(r) {
|
|
||||||
log.Debug("auth: authentication is handled by GL-Inet submodule")
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect to login page if not authenticated
|
|
||||||
isAuthenticated := false
|
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
|
||||||
if err != nil {
|
|
||||||
// The only error that is returned from r.Cookie is [http.ErrNoCookie].
|
|
||||||
// Check Basic authentication.
|
|
||||||
user, pass, hasBasic := r.BasicAuth()
|
|
||||||
if hasBasic {
|
|
||||||
_, isAuthenticated = Context.auth.findUser(user, pass)
|
|
||||||
if !isAuthenticated {
|
|
||||||
log.Info("auth: invalid Basic Authorization value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res := Context.auth.checkSession(cookie.Value)
|
|
||||||
isAuthenticated = res == checkSessionOK
|
|
||||||
if !isAuthenticated {
|
|
||||||
log.Debug("auth: invalid cookie value: %s", cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAuthenticated {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if p := r.URL.Path; p == "/" || p == "/index.html" {
|
|
||||||
if glProcessRedirect(w, r) {
|
|
||||||
log.Debug("auth: redirected to login page by GL-Inet submodule")
|
|
||||||
} else {
|
|
||||||
log.Debug("auth: redirected to login page")
|
|
||||||
http.Redirect(w, r, "login.html", http.StatusFound)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debug("auth: responded with forbidden to %s %s", r.Method, p)
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
_, _ = w.Write([]byte("Forbidden"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(a.garipov): Use [http.Handler] consistently everywhere throughout the
|
|
||||||
// project.
|
|
||||||
func optionalAuth(
|
|
||||||
h func(http.ResponseWriter, *http.Request),
|
|
||||||
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
p := r.URL.Path
|
|
||||||
authRequired := Context.auth != nil && Context.auth.AuthRequired()
|
|
||||||
if p == "/login.html" {
|
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
|
||||||
if authRequired && err == nil {
|
|
||||||
// Redirect to the dashboard if already authenticated.
|
|
||||||
res := Context.auth.checkSession(cookie.Value)
|
|
||||||
if res == checkSessionOK {
|
|
||||||
http.Redirect(w, r, "", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("auth: invalid cookie value: %s", cookie)
|
|
||||||
}
|
|
||||||
} else if isPublicResource(p) {
|
|
||||||
// Process as usual, no additional auth requirements.
|
|
||||||
} else if authRequired {
|
|
||||||
if optionalAuthThird(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isPublicResource returns true if p is a path to a public resource.
|
|
||||||
func isPublicResource(p string) (ok bool) {
|
|
||||||
isAsset, err := path.Match("/assets/*", p)
|
|
||||||
if err != nil {
|
|
||||||
// The only error that is returned from path.Match is
|
|
||||||
// [path.ErrBadPattern]. This is a programmer error.
|
|
||||||
panic(fmt.Errorf("bad asset pattern: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
isLogin, err := path.Match("/login.*", p)
|
|
||||||
if err != nil {
|
|
||||||
// Same as above.
|
|
||||||
panic(fmt.Errorf("bad login pattern: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAsset || isLogin
|
|
||||||
}
|
|
||||||
|
|
||||||
type authHandler struct {
|
|
||||||
handler http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
optionalAuth(a.handler.ServeHTTP)(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func optionalAuthHandler(handler http.Handler) http.Handler {
|
|
||||||
return &authHandler{handler}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a new user with the given password.
|
|
||||||
func (a *Auth) Add(u *webUser, password string) (err error) {
|
|
||||||
if len(password) == 0 {
|
if len(password) == 0 {
|
||||||
return errors.Error("empty password")
|
return errors.Error("empty password")
|
||||||
}
|
}
|
||||||
@ -715,22 +368,40 @@ func (a *Auth) getCurrentUser(r *http.Request) (u webUser) {
|
|||||||
return webUser{}
|
return webUser{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsers - get users
|
// usersList returns a copy of a users list.
|
||||||
func (a *Auth) GetUsers() []webUser {
|
func (a *Auth) usersList() (users []webUser) {
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
users := a.users
|
defer a.lock.Unlock()
|
||||||
a.lock.Unlock()
|
|
||||||
|
users = make([]webUser, len(a.users))
|
||||||
|
copy(users, a.users)
|
||||||
|
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthRequired - if authentication is required
|
// authRequired returns true if a authentication is required.
|
||||||
func (a *Auth) AuthRequired() bool {
|
func (a *Auth) authRequired() bool {
|
||||||
if GLMode {
|
if GLMode {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
r := (len(a.users) != 0)
|
defer a.lock.Unlock()
|
||||||
a.lock.Unlock()
|
|
||||||
return r
|
return len(a.users) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSessionToken returns cryptographically secure randomly generated slice of
|
||||||
|
// bytes of sessionTokenSize length.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Think about using byte array instead of byte slice.
|
||||||
|
func newSessionToken() (data []byte, err error) {
|
||||||
|
randData := make([]byte, sessionTokenSize)
|
||||||
|
|
||||||
|
_, err = rand.Read(randData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return randData, nil
|
||||||
}
|
}
|
||||||
|
89
internal/home/auth_internal_test.go
Normal file
89
internal/home/auth_internal_test.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSessionToken(t *testing.T) {
|
||||||
|
// Successful case.
|
||||||
|
token, err := newSessionToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, token, sessionTokenSize)
|
||||||
|
|
||||||
|
// Break the rand.Reader.
|
||||||
|
prevReader := rand.Reader
|
||||||
|
t.Cleanup(func() { rand.Reader = prevReader })
|
||||||
|
rand.Reader = &bytes.Buffer{}
|
||||||
|
|
||||||
|
// Unsuccessful case.
|
||||||
|
token, err = newSessionToken()
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
fn := filepath.Join(dir, "sessions.db")
|
||||||
|
|
||||||
|
users := []webUser{{
|
||||||
|
Name: "name",
|
||||||
|
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
|
||||||
|
}}
|
||||||
|
a := InitAuth(fn, nil, 60, nil)
|
||||||
|
s := session{}
|
||||||
|
|
||||||
|
user := webUser{Name: "name"}
|
||||||
|
err := a.addUser(&user, "password")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
||||||
|
a.removeSession("notfound")
|
||||||
|
|
||||||
|
sess, err := newSessionToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
sessStr := hex.EncodeToString(sess)
|
||||||
|
|
||||||
|
now := time.Now().UTC().Unix()
|
||||||
|
// check expiration
|
||||||
|
s.expire = uint32(now)
|
||||||
|
a.addSession(sess, &s)
|
||||||
|
assert.Equal(t, checkSessionExpired, a.checkSession(sessStr))
|
||||||
|
|
||||||
|
// add session with TTL = 2 sec
|
||||||
|
s = session{}
|
||||||
|
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
||||||
|
a.addSession(sess, &s)
|
||||||
|
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
||||||
|
|
||||||
|
a.Close()
|
||||||
|
|
||||||
|
// load saved session
|
||||||
|
a = InitAuth(fn, users, 60, nil)
|
||||||
|
|
||||||
|
// the session is still alive
|
||||||
|
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
||||||
|
// reset our expiration time because checkSession() has just updated it
|
||||||
|
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
||||||
|
a.storeSession(sess, &s)
|
||||||
|
a.Close()
|
||||||
|
|
||||||
|
u, ok := a.findUser("name", "password")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotEmpty(t, u.Name)
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// load and remove expired sessions
|
||||||
|
a = InitAuth(fn, users, 60, nil)
|
||||||
|
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))
|
||||||
|
|
||||||
|
a.Close()
|
||||||
|
}
|
352
internal/home/authhttp.go
Normal file
352
internal/home/authhttp.go
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cookieTTL is the time-to-live of the session cookie.
|
||||||
|
const cookieTTL = 365 * timeutil.Day
|
||||||
|
|
||||||
|
// sessionCookieName is the name of the session cookie.
|
||||||
|
const sessionCookieName = "agh_session"
|
||||||
|
|
||||||
|
// loginJSON is the JSON structure for authentication.
|
||||||
|
type loginJSON struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCookie creates a new authentication cookie.
|
||||||
|
func (a *Auth) newCookie(req loginJSON, addr string) (c *http.Cookie, err error) {
|
||||||
|
rateLimiter := a.rateLimiter
|
||||||
|
u, ok := a.findUser(req.Name, req.Password)
|
||||||
|
if !ok {
|
||||||
|
if rateLimiter != nil {
|
||||||
|
rateLimiter.inc(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Error("invalid username or password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rateLimiter != nil {
|
||||||
|
rateLimiter.remove(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := newSessionToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("generating token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
a.addSession(sess, &session{
|
||||||
|
userName: u.Name,
|
||||||
|
expire: uint32(now.Unix()) + a.sessionTTL,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &http.Cookie{
|
||||||
|
Name: sessionCookieName,
|
||||||
|
Value: hex.EncodeToString(sess),
|
||||||
|
Path: "/",
|
||||||
|
Expires: now.Add(cookieTTL),
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// realIP extracts the real IP address of the client from an HTTP request using
|
||||||
|
// the known HTTP headers.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Currently, this is basically a copy of a similar function in
|
||||||
|
// module dnsproxy. This should really become a part of module golibs and be
|
||||||
|
// replaced both here and there. Or be replaced in both places by
|
||||||
|
// a well-maintained third-party module.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Support header Forwarded from RFC 7329.
|
||||||
|
func realIP(r *http.Request) (ip net.IP, err error) {
|
||||||
|
proxyHeaders := []string{
|
||||||
|
httphdr.CFConnectingIP,
|
||||||
|
httphdr.TrueClientIP,
|
||||||
|
httphdr.XRealIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range proxyHeaders {
|
||||||
|
v := r.Header.Get(h)
|
||||||
|
ip = net.ParseIP(v)
|
||||||
|
if ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above yielded any results, get the leftmost IP address
|
||||||
|
// from the X-Forwarded-For header.
|
||||||
|
s := r.Header.Get(httphdr.XForwardedFor)
|
||||||
|
ipStrs := strings.SplitN(s, ", ", 2)
|
||||||
|
ip = net.ParseIP(ipStrs[0])
|
||||||
|
if ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// When everything else fails, just return the remote address as understood
|
||||||
|
// by the stdlib.
|
||||||
|
ipStr, err := netutil.SplitHost(r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting ip from client addr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(ipStr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeErrorWithIP is like [aghhttp.Error], but includes the remote IP address
|
||||||
|
// when it writes to the log.
|
||||||
|
func writeErrorWithIP(
|
||||||
|
r *http.Request,
|
||||||
|
w http.ResponseWriter,
|
||||||
|
code int,
|
||||||
|
remoteIP string,
|
||||||
|
format string,
|
||||||
|
args ...any,
|
||||||
|
) {
|
||||||
|
text := fmt.Sprintf(format, args...)
|
||||||
|
log.Error("%s %s %s: from ip %s: %s", r.Method, r.Host, r.URL, remoteIP, text)
|
||||||
|
http.Error(w, text, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleLogin is the handler for the POST /control/login HTTP API.
|
||||||
|
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := loginJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteIP string
|
||||||
|
// realIP cannot be used here without taking TrustedProxies into account due
|
||||||
|
// to security issues.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2799.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use realIP when the issue will be fixed.
|
||||||
|
if remoteIP, err = netutil.SplitHost(r.RemoteAddr); err != nil {
|
||||||
|
writeErrorWithIP(
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
r.RemoteAddr,
|
||||||
|
"auth: getting remote address: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rateLimiter := Context.auth.rateLimiter; rateLimiter != nil {
|
||||||
|
if left := rateLimiter.check(remoteIP); left > 0 {
|
||||||
|
w.Header().Set(httphdr.RetryAfter, strconv.Itoa(int(left.Seconds())))
|
||||||
|
writeErrorWithIP(
|
||||||
|
r,
|
||||||
|
w,
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
remoteIP,
|
||||||
|
"auth: blocked for %s",
|
||||||
|
left,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := Context.auth.newCookie(req, remoteIP)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorWithIP(r, w, http.StatusForbidden, remoteIP, "%s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use realIP here, since this IP address is only used for logging.
|
||||||
|
ip, err := realIP(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("auth: getting real ip from request with remote ip %s: %s", remoteIP, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("auth: user %q successfully logged in from ip %v", req.Name, ip)
|
||||||
|
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
h := w.Header()
|
||||||
|
h.Set(httphdr.CacheControl, "no-store, no-cache, must-revalidate, proxy-revalidate")
|
||||||
|
h.Set(httphdr.Pragma, "no-cache")
|
||||||
|
h.Set(httphdr.Expires, "0")
|
||||||
|
|
||||||
|
aghhttp.OK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleLogout is the handler for the GET /control/logout HTTP API.
|
||||||
|
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
respHdr := w.Header()
|
||||||
|
c, err := r.Cookie(sessionCookieName)
|
||||||
|
if err != nil {
|
||||||
|
// The only error that is returned from r.Cookie is [http.ErrNoCookie].
|
||||||
|
// The user is already logged out.
|
||||||
|
respHdr.Set(httphdr.Location, "/login.html")
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Context.auth.removeSession(c.Value)
|
||||||
|
|
||||||
|
c = &http.Cookie{
|
||||||
|
Name: sessionCookieName,
|
||||||
|
Value: "",
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Unix(0, 0),
|
||||||
|
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
respHdr.Set(httphdr.Location, "/login.html")
|
||||||
|
respHdr.Set(httphdr.SetCookie, c.String())
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterAuthHandlers - register handlers
|
||||||
|
func RegisterAuthHandlers() {
|
||||||
|
Context.mux.Handle("/control/login", postInstallHandler(ensureHandler(http.MethodPost, handleLogin)))
|
||||||
|
httpRegister(http.MethodGet, "/control/logout", handleLogout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionalAuthThird returns true if a user should authenticate first.
|
||||||
|
func optionalAuthThird(w http.ResponseWriter, r *http.Request) (mustAuth bool) {
|
||||||
|
pref := fmt.Sprintf("auth: raddr %s", r.RemoteAddr)
|
||||||
|
|
||||||
|
if glProcessCookie(r) {
|
||||||
|
log.Debug("%s: authentication is handled by gl-inet submodule", pref)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect to login page if not authenticated
|
||||||
|
isAuthenticated := false
|
||||||
|
cookie, err := r.Cookie(sessionCookieName)
|
||||||
|
if err != nil {
|
||||||
|
// The only error that is returned from r.Cookie is [http.ErrNoCookie].
|
||||||
|
// Check Basic authentication.
|
||||||
|
user, pass, hasBasic := r.BasicAuth()
|
||||||
|
if hasBasic {
|
||||||
|
_, isAuthenticated = Context.auth.findUser(user, pass)
|
||||||
|
if !isAuthenticated {
|
||||||
|
log.Info("%s: invalid basic authorization value", pref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res := Context.auth.checkSession(cookie.Value)
|
||||||
|
isAuthenticated = res == checkSessionOK
|
||||||
|
if !isAuthenticated {
|
||||||
|
log.Debug("%s: invalid cookie value: %q", pref, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAuthenticated {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p := r.URL.Path; p == "/" || p == "/index.html" {
|
||||||
|
if glProcessRedirect(w, r) {
|
||||||
|
log.Debug("%s: redirected to login page by gl-inet submodule", pref)
|
||||||
|
} else {
|
||||||
|
log.Debug("%s: redirected to login page", pref)
|
||||||
|
http.Redirect(w, r, "login.html", http.StatusFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debug("%s: responded with forbidden to %s %s", pref, r.Method, p)
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
_, _ = w.Write([]byte("Forbidden"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(a.garipov): Use [http.Handler] consistently everywhere throughout the
|
||||||
|
// project.
|
||||||
|
func optionalAuth(
|
||||||
|
h func(http.ResponseWriter, *http.Request),
|
||||||
|
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := r.URL.Path
|
||||||
|
authRequired := Context.auth != nil && Context.auth.authRequired()
|
||||||
|
if p == "/login.html" {
|
||||||
|
cookie, err := r.Cookie(sessionCookieName)
|
||||||
|
if authRequired && err == nil {
|
||||||
|
// Redirect to the dashboard if already authenticated.
|
||||||
|
res := Context.auth.checkSession(cookie.Value)
|
||||||
|
if res == checkSessionOK {
|
||||||
|
http.Redirect(w, r, "", http.StatusFound)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("auth: raddr %s: invalid cookie value: %q", r.RemoteAddr, cookie)
|
||||||
|
}
|
||||||
|
} else if isPublicResource(p) {
|
||||||
|
// Process as usual, no additional auth requirements.
|
||||||
|
} else if authRequired {
|
||||||
|
if optionalAuthThird(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPublicResource returns true if p is a path to a public resource.
|
||||||
|
func isPublicResource(p string) (ok bool) {
|
||||||
|
isAsset, err := path.Match("/assets/*", p)
|
||||||
|
if err != nil {
|
||||||
|
// The only error that is returned from path.Match is
|
||||||
|
// [path.ErrBadPattern]. This is a programmer error.
|
||||||
|
panic(fmt.Errorf("bad asset pattern: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
isLogin, err := path.Match("/login.*", p)
|
||||||
|
if err != nil {
|
||||||
|
// Same as above.
|
||||||
|
panic(fmt.Errorf("bad login pattern: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAsset || isLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
// authHandler is a helper structure that implements [http.Handler].
|
||||||
|
type authHandler struct {
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements the [http.Handler] interface for *authHandler.
|
||||||
|
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
optionalAuth(a.handler.ServeHTTP)(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionalAuthHandler returns a authentication handler.
|
||||||
|
func optionalAuthHandler(handler http.Handler) http.Handler {
|
||||||
|
return &authHandler{handler}
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
@ -18,82 +14,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewSessionToken(t *testing.T) {
|
|
||||||
// Successful case.
|
|
||||||
token, err := newSessionToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, token, sessionTokenSize)
|
|
||||||
|
|
||||||
// Break the rand.Reader.
|
|
||||||
prevReader := rand.Reader
|
|
||||||
t.Cleanup(func() { rand.Reader = prevReader })
|
|
||||||
rand.Reader = &bytes.Buffer{}
|
|
||||||
|
|
||||||
// Unsuccessful case.
|
|
||||||
token, err = newSessionToken()
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Empty(t, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
|
||||||
|
|
||||||
users := []webUser{{
|
|
||||||
Name: "name",
|
|
||||||
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
|
|
||||||
}}
|
|
||||||
a := InitAuth(fn, nil, 60, nil)
|
|
||||||
s := session{}
|
|
||||||
|
|
||||||
user := webUser{Name: "name"}
|
|
||||||
err := a.Add(&user, "password")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
|
||||||
a.RemoveSession("notfound")
|
|
||||||
|
|
||||||
sess, err := newSessionToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
sessStr := hex.EncodeToString(sess)
|
|
||||||
|
|
||||||
now := time.Now().UTC().Unix()
|
|
||||||
// check expiration
|
|
||||||
s.expire = uint32(now)
|
|
||||||
a.addSession(sess, &s)
|
|
||||||
assert.Equal(t, checkSessionExpired, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
// add session with TTL = 2 sec
|
|
||||||
s = session{}
|
|
||||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
|
||||||
a.addSession(sess, &s)
|
|
||||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
a.Close()
|
|
||||||
|
|
||||||
// load saved session
|
|
||||||
a = InitAuth(fn, users, 60, nil)
|
|
||||||
|
|
||||||
// the session is still alive
|
|
||||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
|
||||||
// reset our expiration time because checkSession() has just updated it
|
|
||||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
|
||||||
a.storeSession(sess, &s)
|
|
||||||
a.Close()
|
|
||||||
|
|
||||||
u, ok := a.findUser("name", "password")
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.NotEmpty(t, u.Name)
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// load and remove expired sessions
|
|
||||||
a = InitAuth(fn, users, 60, nil)
|
|
||||||
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
a.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements http.ResponseWriter
|
// implements http.ResponseWriter
|
||||||
type testResponseWriter struct {
|
type testResponseWriter struct {
|
||||||
hdr http.Header
|
hdr http.Header
|
@ -306,10 +306,12 @@ var config = &configuration{
|
|||||||
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
|
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
|
||||||
Port: defaultPortDNS,
|
Port: defaultPortDNS,
|
||||||
Config: dnsforward.Config{
|
Config: dnsforward.Config{
|
||||||
Ratelimit: 20,
|
Ratelimit: 20,
|
||||||
RefuseAny: true,
|
RatelimitSubnetLenIPv4: 24,
|
||||||
AllServers: false,
|
RatelimitSubnetLenIPv6: 56,
|
||||||
HandleDDR: true,
|
RefuseAny: true,
|
||||||
|
AllServers: false,
|
||||||
|
HandleDDR: true,
|
||||||
FastestTimeout: timeutil.Duration{
|
FastestTimeout: timeutil.Duration{
|
||||||
Duration: fastip.DefaultPingWaitTimeout,
|
Duration: fastip.DefaultPingWaitTimeout,
|
||||||
},
|
},
|
||||||
@ -587,7 +589,7 @@ func (c *configuration) write() (err error) {
|
|||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
if Context.auth != nil {
|
if Context.auth != nil {
|
||||||
config.Users = Context.auth.GetUsers()
|
config.Users = Context.auth.usersList()
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.tls != nil {
|
if Context.tls != nil {
|
||||||
|
@ -420,7 +420,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
u := &webUser{
|
u := &webUser{
|
||||||
Name: req.Username,
|
Name: req.Username,
|
||||||
}
|
}
|
||||||
err = Context.auth.Add(u, req.Password)
|
err = Context.auth.addUser(u, req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Context.firstRun = true
|
Context.firstRun = true
|
||||||
copyInstallSettings(config, curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package ipset
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -38,19 +39,69 @@ func newManager(ipsetConf []string) (set Manager, err error) {
|
|||||||
|
|
||||||
// defaultDial is the default netfilter dialing function.
|
// defaultDial is the default netfilter dialing function.
|
||||||
func defaultDial(pf netfilter.ProtoFamily, conf *netlink.Config) (conn ipsetConn, err error) {
|
func defaultDial(pf netfilter.ProtoFamily, conf *netlink.Config) (conn ipsetConn, err error) {
|
||||||
conn, err = ipset.Dial(pf, conf)
|
c, err := ipset.Dial(pf, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn, nil
|
return &queryConn{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryConn is the [ipsetConn] implementation with listAll method, which
|
||||||
|
// returns the list of properties of all available ipsets.
|
||||||
|
type queryConn struct {
|
||||||
|
*ipset.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ ipsetConn = (*queryConn)(nil)
|
||||||
|
|
||||||
|
// listAll returns the list of properties of all available ipsets.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Use https://github.com/vishvananda/netlink.
|
||||||
|
func (qc *queryConn) listAll() (sets []props, err error) {
|
||||||
|
msg, err := netfilter.MarshalNetlink(
|
||||||
|
netfilter.Header{
|
||||||
|
// The family doesn't seem to matter. See TODO on parseIpsetConfig.
|
||||||
|
Family: qc.Conn.Family,
|
||||||
|
SubsystemID: netfilter.NFSubsysIPSet,
|
||||||
|
MessageType: netfilter.MessageType(ipset.CmdList),
|
||||||
|
Flags: netlink.Request | netlink.Dump,
|
||||||
|
},
|
||||||
|
[]netfilter.Attribute{{
|
||||||
|
Type: uint16(ipset.AttrProtocol),
|
||||||
|
Data: []byte{ipset.Protocol},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshaling netlink msg: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume it's OK to call a method of an unexported type
|
||||||
|
// [ipset.connector], since there is no negative effects.
|
||||||
|
ms, err := qc.Conn.Conn.Query(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying netlink msg: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range ms {
|
||||||
|
p := props{}
|
||||||
|
err = p.unmarshalMessage(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshaling netlink msg at index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sets = append(sets, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsetConn is the ipset conn interface.
|
// ipsetConn is the ipset conn interface.
|
||||||
type ipsetConn interface {
|
type ipsetConn interface {
|
||||||
Add(name string, entries ...*ipset.Entry) (err error)
|
Add(name string, entries ...*ipset.Entry) (err error)
|
||||||
Close() (err error)
|
Close() (err error)
|
||||||
Header(name string) (p *ipset.HeaderPolicy, err error)
|
listAll() (sets []props, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialer creates an ipsetConn.
|
// dialer creates an ipsetConn.
|
||||||
@ -58,8 +109,75 @@ type dialer func(pf netfilter.ProtoFamily, conf *netlink.Config) (conn ipsetConn
|
|||||||
|
|
||||||
// props contains one Linux Netfilter ipset properties.
|
// props contains one Linux Netfilter ipset properties.
|
||||||
type props struct {
|
type props struct {
|
||||||
name string
|
// name of the ipset.
|
||||||
|
name string
|
||||||
|
|
||||||
|
// family of the IP addresses in the ipset.
|
||||||
family netfilter.ProtoFamily
|
family netfilter.ProtoFamily
|
||||||
|
|
||||||
|
// isPersistent indicates that ipset has no timeout parameter and all
|
||||||
|
// entries are added permanently.
|
||||||
|
isPersistent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalMessage unmarshals netlink message and sets the properties of the
|
||||||
|
// ipset.
|
||||||
|
func (p *props) unmarshalMessage(msg netlink.Message) (err error) {
|
||||||
|
_, attrs, err := netfilter.UnmarshalNetlink(msg)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default ipset has no timeout parameter.
|
||||||
|
p.isPersistent = true
|
||||||
|
|
||||||
|
for _, a := range attrs {
|
||||||
|
p.parseAttribute(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAttribute parses netfilter attribute and sets the name and family of
|
||||||
|
// the ipset.
|
||||||
|
func (p *props) parseAttribute(a netfilter.Attribute) {
|
||||||
|
switch ipset.AttributeType(a.Type) {
|
||||||
|
case ipset.AttrData:
|
||||||
|
p.parseAttrData(a)
|
||||||
|
case ipset.AttrSetName:
|
||||||
|
// Trim the null character.
|
||||||
|
p.name = string(bytes.Trim(a.Data, "\x00"))
|
||||||
|
case ipset.AttrFamily:
|
||||||
|
p.family = netfilter.ProtoFamily(a.Data[0])
|
||||||
|
default:
|
||||||
|
// Go on.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAttrData parses attribute data and sets the timeout of the ipset.
|
||||||
|
func (p *props) parseAttrData(a netfilter.Attribute) {
|
||||||
|
for _, a := range a.Children {
|
||||||
|
switch ipset.AttributeType(a.Type) {
|
||||||
|
case ipset.AttrTimeout:
|
||||||
|
timeout := a.Uint32()
|
||||||
|
p.isPersistent = timeout == 0
|
||||||
|
default:
|
||||||
|
// Go on.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unit is a convenient alias for struct{}.
|
||||||
|
type unit = struct{}
|
||||||
|
|
||||||
|
// ipsInIpset is the type of a set of IP-address-to-ipset mappings.
|
||||||
|
type ipsInIpset map[ipInIpsetEntry]unit
|
||||||
|
|
||||||
|
// ipInIpsetEntry is the type for entries in an ipsInIpset set.
|
||||||
|
type ipInIpsetEntry struct {
|
||||||
|
ipsetName string
|
||||||
|
ipArr [net.IPv6len]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// manager is the Linux Netfilter ipset manager.
|
// manager is the Linux Netfilter ipset manager.
|
||||||
@ -72,6 +190,13 @@ type manager struct {
|
|||||||
// mu protects all properties below.
|
// mu protects all properties below.
|
||||||
mu *sync.Mutex
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
// TODO(a.garipov): Currently, the ipset list is static, and we don't
|
||||||
|
// read the IPs already in sets, so we can assume that all incoming IPs
|
||||||
|
// are either added to all corresponding ipsets or not. When that stops
|
||||||
|
// being the case, for example if we add dynamic reconfiguration of
|
||||||
|
// ipsets, this map will need to become a per-ipset-name one.
|
||||||
|
addedIPs ipsInIpset
|
||||||
|
|
||||||
ipv4Conn ipsetConn
|
ipv4Conn ipsetConn
|
||||||
ipv6Conn ipsetConn
|
ipv6Conn ipsetConn
|
||||||
}
|
}
|
||||||
@ -96,8 +221,8 @@ func (m *manager) dialNetfilter(conf *netlink.Config) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIpsetConfig parses one ipset configuration string.
|
// parseIpsetConfigLine parses one ipset configuration line.
|
||||||
func parseIpsetConfig(confStr string) (hosts, ipsetNames []string, err error) {
|
func parseIpsetConfigLine(confStr string) (hosts, ipsetNames []string, err error) {
|
||||||
confStr = strings.TrimSpace(confStr)
|
confStr = strings.TrimSpace(confStr)
|
||||||
hostsAndNames := strings.Split(confStr, "/")
|
hostsAndNames := strings.Split(confStr, "/")
|
||||||
if len(hostsAndNames) != 2 {
|
if len(hostsAndNames) != 2 {
|
||||||
@ -125,50 +250,53 @@ func parseIpsetConfig(confStr string) (hosts, ipsetNames []string, err error) {
|
|||||||
return hosts, ipsetNames, nil
|
return hosts, ipsetNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsetProps returns the properties of an ipset with the given name.
|
// parseIpsetConfig parses the ipset configuration and stores ipsets. It
|
||||||
func (m *manager) ipsetProps(name string) (set props, err error) {
|
// returns an error if the configuration can't be used.
|
||||||
// The family doesn't seem to matter when we use a header query, so
|
func (m *manager) parseIpsetConfig(ipsetConf []string) (err error) {
|
||||||
// query only the IPv4 one.
|
// The family doesn't seem to matter when we use a header query, so query
|
||||||
|
// only the IPv4 one.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Find out if this is a bug or a feature.
|
// TODO(a.garipov): Find out if this is a bug or a feature.
|
||||||
var res *ipset.HeaderPolicy
|
all, err := m.ipv4Conn.listAll()
|
||||||
res, err = m.ipv4Conn.Header(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return set, err
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if res == nil || res.Family == nil {
|
for _, p := range all {
|
||||||
return set, errors.Error("empty response or no family data")
|
m.nameToIpset[p.name] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
family := netfilter.ProtoFamily(res.Family.Value)
|
for i, confStr := range ipsetConf {
|
||||||
if family != netfilter.ProtoIPv4 && family != netfilter.ProtoIPv6 {
|
var hosts, ipsetNames []string
|
||||||
return set, fmt.Errorf("unexpected ipset family %d", family)
|
hosts, ipsetNames, err = parseIpsetConfigLine(confStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("config line at idx %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipsets []props
|
||||||
|
ipsets, err = m.ipsets(ipsetNames)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting ipsets from config line at idx %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range hosts {
|
||||||
|
m.domainToIpsets[host] = append(m.domainToIpsets[host], ipsets...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return props{
|
return nil
|
||||||
name: name,
|
|
||||||
family: family,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsets returns currently known ipsets.
|
// ipsets returns currently known ipsets.
|
||||||
func (m *manager) ipsets(names []string) (sets []props, err error) {
|
func (m *manager) ipsets(names []string) (sets []props, err error) {
|
||||||
for _, name := range names {
|
for _, n := range names {
|
||||||
set, ok := m.nameToIpset[name]
|
p, ok := m.nameToIpset[n]
|
||||||
if ok {
|
if !ok {
|
||||||
sets = append(sets, set)
|
return nil, fmt.Errorf("unknown ipset %q", n)
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set, err = m.ipsetProps(name)
|
sets = append(sets, p)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("querying ipset %q: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.nameToIpset[name] = set
|
|
||||||
sets = append(sets, set)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sets, nil
|
return sets, nil
|
||||||
@ -186,6 +314,8 @@ func newManagerWithDialer(ipsetConf []string, dial dialer) (mgr Manager, err err
|
|||||||
domainToIpsets: make(map[string][]props),
|
domainToIpsets: make(map[string][]props),
|
||||||
|
|
||||||
dial: dial,
|
dial: dial,
|
||||||
|
|
||||||
|
addedIPs: make(ipsInIpset),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.dialNetfilter(&netlink.Config{})
|
err = m.dialNetfilter(&netlink.Config{})
|
||||||
@ -201,26 +331,9 @@ func newManagerWithDialer(ipsetConf []string, dial dialer) (mgr Manager, err err
|
|||||||
return nil, fmt.Errorf("dialing netfilter: %w", err)
|
return nil, fmt.Errorf("dialing netfilter: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, confStr := range ipsetConf {
|
err = m.parseIpsetConfig(ipsetConf)
|
||||||
var hosts, ipsetNames []string
|
if err != nil {
|
||||||
hosts, ipsetNames, err = parseIpsetConfig(confStr)
|
return nil, fmt.Errorf("getting ipsets: %w", err)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("config line at idx %d: %w", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipsets []props
|
|
||||||
ipsets, err = m.ipsets(ipsetNames)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"getting ipsets from config line at idx %d: %w",
|
|
||||||
i,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, host := range hosts {
|
|
||||||
m.domainToIpsets[host] = append(m.domainToIpsets[host], ipsets...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
@ -259,8 +372,19 @@ func (m *manager) addIPs(host string, set props, ips []net.IP) (n int, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entries []*ipset.Entry
|
var entries []*ipset.Entry
|
||||||
|
var newAddedEntries []ipInIpsetEntry
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
|
e := ipInIpsetEntry{
|
||||||
|
ipsetName: set.name,
|
||||||
|
}
|
||||||
|
copy(e.ipArr[:], ip.To16())
|
||||||
|
|
||||||
|
if _, added := m.addedIPs[e]; added {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
entries = append(entries, ipset.NewEntry(ipset.EntryIP(ip)))
|
entries = append(entries, ipset.NewEntry(ipset.EntryIP(ip)))
|
||||||
|
newAddedEntries = append(newAddedEntries, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
n = len(entries)
|
n = len(entries)
|
||||||
@ -283,6 +407,15 @@ func (m *manager) addIPs(host string, set props, ips []net.IP) (n int, err error
|
|||||||
return 0, fmt.Errorf("adding %q%s to ipset %q: %w", host, ips, set.name, err)
|
return 0, fmt.Errorf("adding %q%s to ipset %q: %w", host, ips, set.name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only add these to the cache once we're sure that all of them were
|
||||||
|
// actually sent to the ipset.
|
||||||
|
for _, e := range newAddedEntries {
|
||||||
|
s := m.nameToIpset[e.ipsetName]
|
||||||
|
if s.isPersistent {
|
||||||
|
m.addedIPs[e] = unit{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,12 @@ type fakeConn struct {
|
|||||||
ipv4Entries *[]*ipset.Entry
|
ipv4Entries *[]*ipset.Entry
|
||||||
ipv6Header *ipset.HeaderPolicy
|
ipv6Header *ipset.HeaderPolicy
|
||||||
ipv6Entries *[]*ipset.Entry
|
ipv6Entries *[]*ipset.Entry
|
||||||
|
sets []props
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ ipsetConn = (*fakeConn)(nil)
|
||||||
|
|
||||||
// Add implements the [ipsetConn] interface for *fakeConn.
|
// Add implements the [ipsetConn] interface for *fakeConn.
|
||||||
func (c *fakeConn) Add(name string, entries ...*ipset.Entry) (err error) {
|
func (c *fakeConn) Add(name string, entries ...*ipset.Entry) (err error) {
|
||||||
if strings.Contains(name, "ipv4") {
|
if strings.Contains(name, "ipv4") {
|
||||||
@ -43,15 +47,9 @@ func (c *fakeConn) Close() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header implements the [ipsetConn] interface for *fakeConn.
|
// listAll implements the [ipsetConn] interface for *fakeConn.
|
||||||
func (c *fakeConn) Header(name string) (p *ipset.HeaderPolicy, err error) {
|
func (c *fakeConn) listAll() (sets []props, err error) {
|
||||||
if strings.Contains(name, "ipv4") {
|
return c.sets, nil
|
||||||
return c.ipv4Header, nil
|
|
||||||
} else if strings.Contains(name, "ipv6") {
|
|
||||||
return c.ipv6Header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Error("test: ipset not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_Add(t *testing.T) {
|
func TestManager_Add(t *testing.T) {
|
||||||
@ -76,6 +74,13 @@ func TestManager_Add(t *testing.T) {
|
|||||||
Family: ipset.NewUInt8Box(uint8(netfilter.ProtoIPv6)),
|
Family: ipset.NewUInt8Box(uint8(netfilter.ProtoIPv6)),
|
||||||
},
|
},
|
||||||
ipv6Entries: &ipv6Entries,
|
ipv6Entries: &ipv6Entries,
|
||||||
|
sets: []props{{
|
||||||
|
name: "ipv4set",
|
||||||
|
family: netfilter.ProtoIPv4,
|
||||||
|
}, {
|
||||||
|
name: "ipv6set",
|
||||||
|
family: netfilter.ProtoIPv6,
|
||||||
|
}},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@ func TestStats_races(t *testing.T) {
|
|||||||
|
|
||||||
writeFunc := func(start, fin *sync.WaitGroup, waitCh <-chan unit, i int) {
|
writeFunc := func(start, fin *sync.WaitGroup, waitCh <-chan unit, i int) {
|
||||||
e := &Entry{
|
e := &Entry{
|
||||||
Domain: fmt.Sprintf("example-%d.org", i),
|
Domain: fmt.Sprintf("example-%d.org", i),
|
||||||
Client: fmt.Sprintf("client_%d", i),
|
Client: fmt.Sprintf("client_%d", i),
|
||||||
Result: Result(i)%(resultLast-1) + 1,
|
Result: Result(i)%(resultLast-1) + 1,
|
||||||
Time: time.Since(startTime),
|
ProcessingTime: time.Since(startTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
start.Done()
|
start.Done()
|
||||||
|
@ -76,17 +76,19 @@ func TestStats(t *testing.T) {
|
|||||||
const respUpstream = "upstream"
|
const respUpstream = "upstream"
|
||||||
|
|
||||||
entries := []*stats.Entry{{
|
entries := []*stats.Entry{{
|
||||||
Domain: reqDomain,
|
Domain: reqDomain,
|
||||||
Client: cliIPStr,
|
Client: cliIPStr,
|
||||||
Result: stats.RFiltered,
|
Result: stats.RFiltered,
|
||||||
Time: time.Microsecond * 123456,
|
ProcessingTime: time.Microsecond * 123456,
|
||||||
Upstream: respUpstream,
|
Upstream: respUpstream,
|
||||||
|
UpstreamTime: time.Microsecond * 222222,
|
||||||
}, {
|
}, {
|
||||||
Domain: reqDomain,
|
Domain: reqDomain,
|
||||||
Client: cliIPStr,
|
Client: cliIPStr,
|
||||||
Result: stats.RNotFiltered,
|
Result: stats.RNotFiltered,
|
||||||
Time: time.Microsecond * 123456,
|
ProcessingTime: time.Microsecond * 123456,
|
||||||
Upstream: respUpstream,
|
Upstream: respUpstream,
|
||||||
|
UpstreamTime: time.Microsecond * 222222,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
wantData := &stats.StatsResp{
|
wantData := &stats.StatsResp{
|
||||||
@ -95,7 +97,7 @@ func TestStats(t *testing.T) {
|
|||||||
TopClients: []map[string]uint64{0: {cliIPStr: 2}},
|
TopClients: []map[string]uint64{0: {cliIPStr: 2}},
|
||||||
TopBlocked: []map[string]uint64{0: {reqDomain: 1}},
|
TopBlocked: []map[string]uint64{0: {reqDomain: 1}},
|
||||||
TopUpstreamsResponses: []map[string]uint64{0: {respUpstream: 2}},
|
TopUpstreamsResponses: []map[string]uint64{0: {respUpstream: 2}},
|
||||||
TopUpstreamsAvgTime: []map[string]float64{0: {respUpstream: 0.123456}},
|
TopUpstreamsAvgTime: []map[string]float64{0: {respUpstream: 0.222222}},
|
||||||
DNSQueries: []uint64{
|
DNSQueries: []uint64{
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||||
@ -196,10 +198,10 @@ func TestLargeNumbers(t *testing.T) {
|
|||||||
for i := 0; i < cliNumPerHour; i++ {
|
for i := 0; i < cliNumPerHour; i++ {
|
||||||
ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)}
|
ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)}
|
||||||
e := &stats.Entry{
|
e := &stats.Entry{
|
||||||
Domain: fmt.Sprintf("domain%d.hour%d", i, h),
|
Domain: fmt.Sprintf("domain%d.hour%d", i, h),
|
||||||
Client: ip.String(),
|
Client: ip.String(),
|
||||||
Result: stats.RNotFiltered,
|
Result: stats.RNotFiltered,
|
||||||
Time: 123456,
|
ProcessingTime: 123456,
|
||||||
}
|
}
|
||||||
s.Update(e)
|
s.Update(e)
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,12 @@ type Entry struct {
|
|||||||
// Result is the result of processing the request.
|
// Result is the result of processing the request.
|
||||||
Result Result
|
Result Result
|
||||||
|
|
||||||
// Time is the duration of the request processing.
|
// ProcessingTime is the duration of the request processing from the start
|
||||||
Time time.Duration
|
// of the request including timeouts.
|
||||||
|
ProcessingTime time.Duration
|
||||||
|
|
||||||
|
// UpstreamTime is the duration of the successful request to the upstream.
|
||||||
|
UpstreamTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate returns an error if entry is not valid.
|
// validate returns an error if entry is not valid.
|
||||||
@ -103,8 +107,8 @@ type unit struct {
|
|||||||
// upstreamsResponses stores the number of responses from each upstream.
|
// upstreamsResponses stores the number of responses from each upstream.
|
||||||
upstreamsResponses map[string]uint64
|
upstreamsResponses map[string]uint64
|
||||||
|
|
||||||
// upstreamsTimeSum stores the sum of processing time in microseconds of
|
// upstreamsTimeSum stores the sum of durations of successful queries in
|
||||||
// responses from each upstream.
|
// microseconds to each upstream.
|
||||||
upstreamsTimeSum map[string]uint64
|
upstreamsTimeSum map[string]uint64
|
||||||
|
|
||||||
// nResult stores the number of requests grouped by it's result.
|
// nResult stores the number of requests grouped by it's result.
|
||||||
@ -323,13 +327,14 @@ func (u *unit) add(e *Entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.clients[e.Client]++
|
u.clients[e.Client]++
|
||||||
t := uint64(e.Time.Microseconds())
|
pt := uint64(e.ProcessingTime.Microseconds())
|
||||||
u.timeSum += t
|
u.timeSum += pt
|
||||||
u.nTotal++
|
u.nTotal++
|
||||||
|
|
||||||
if e.Upstream != "" {
|
if e.Upstream != "" {
|
||||||
u.upstreamsResponses[e.Upstream]++
|
u.upstreamsResponses[e.Upstream]++
|
||||||
u.upstreamsTimeSum[e.Upstream] += t
|
ut := uint64(e.UpstreamTime.Microseconds())
|
||||||
|
u.upstreamsTimeSum[e.Upstream] += ut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/fzipp/gocyclo v0.6.0
|
github.com/fzipp/gocyclo v0.6.0
|
||||||
github.com/golangci/misspell v0.4.1
|
github.com/golangci/misspell v0.4.1
|
||||||
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601
|
github.com/gordonklaus/ineffassign v0.1.0
|
||||||
github.com/kisielk/errcheck v1.6.3
|
github.com/kisielk/errcheck v1.6.3
|
||||||
github.com/kyoh86/looppointer v0.2.1
|
github.com/kyoh86/looppointer v0.2.1
|
||||||
github.com/securego/gosec/v2 v2.18.1
|
github.com/securego/gosec/v2 v2.18.2
|
||||||
github.com/uudashr/gocognit v1.1.1
|
github.com/uudashr/gocognit v1.1.2
|
||||||
golang.org/x/tools v0.14.0
|
golang.org/x/tools v0.15.0
|
||||||
golang.org/x/vuln v1.0.1
|
golang.org/x/vuln v1.0.1
|
||||||
honnef.co/go/tools v0.4.6
|
honnef.co/go/tools v0.4.6
|
||||||
mvdan.cc/gofumpt v0.5.0
|
mvdan.cc/gofumpt v0.5.0
|
||||||
@ -21,14 +21,14 @@ require (
|
|||||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
|
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/uuid v1.3.1 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
github.com/kyoh86/nolint v0.0.1 // indirect
|
github.com/kyoh86/nolint v0.0.1 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d // indirect
|
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||||
golang.org/x/mod v0.13.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/sync v0.4.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
@ -15,12 +15,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||||
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8=
|
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
|
||||||
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
||||||
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
|
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
|
||||||
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
|
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@ -29,15 +29,15 @@ github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fx
|
|||||||
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
||||||
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
|
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
|
||||||
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
|
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
|
||||||
github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA=
|
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||||
github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c=
|
github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/securego/gosec/v2 v2.18.1 h1:xnnehWg7dIW8qrRPGm8ykY21zp2MueKyC99Vlcuj96I=
|
github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I=
|
||||||
github.com/securego/gosec/v2 v2.18.1/go.mod h1:ZUTcKD9gAFip1lLGHWCjkoBQJyaEzePTNzjwlL2HHoE=
|
github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/uudashr/gocognit v1.1.1 h1:qIj6KhmcGQGBiWtaKH6ZlIyDGa6br2febZNZ6MDzqMw=
|
github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI=
|
||||||
github.com/uudashr/gocognit v1.1.1/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
|
github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@ -49,26 +49,26 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d h1:NRn/Afz91uVUyEsxMp4lGGxpr5y1qz+Iko60dbkfvLQ=
|
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa h1:wJBD77KpXKOckDJT0rqU5EwZDmxcmTh6aXVpU6s6GBg=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -79,8 +79,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@ -93,8 +93,8 @@ golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4X
|
|||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
|
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
|
||||||
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
|
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
|
||||||
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=
|
||||||
|
@ -35,7 +35,7 @@ set -f -u
|
|||||||
go_version="$( "${GO:-go}" version )"
|
go_version="$( "${GO:-go}" version )"
|
||||||
readonly go_version
|
readonly go_version
|
||||||
|
|
||||||
go_min_version='go1.20.10'
|
go_min_version='go1.20.11'
|
||||||
go_version_msg="
|
go_version_msg="
|
||||||
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
|
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
|
||||||
if you have the version installed, please set the GO environment variable.
|
if you have the version installed, please set the GO environment variable.
|
||||||
|
Loading…
Reference in New Issue
Block a user