Compare commits

..

3 Commits

Author SHA1 Message Date
Ainar Garipov
0c7d56dca3 Merge branch 'master' into 4927-refactor-tls 2022-11-22 17:10:40 +03:00
Ainar Garipov
f36efa26a4 home: refactor more 2022-11-21 19:45:18 +03:00
Ainar Garipov
a8850059db home: refactor tls 2022-11-21 19:05:49 +03:00
75 changed files with 757 additions and 2060 deletions

View File

@@ -12,81 +12,26 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
<!-- <!--
## [v0.108.0] - TBA ## [v0.108.0] - TBA (APPROX.)
--> -->
<!-- <!--
## [v0.107.21] - 2122-12-28 (APPROX.) ## [v0.107.19] - 2022-11-23 (APPROX.)
See also the [v0.107.21 GitHub milestone][ms-v0.107.21].
[ms-v0.107.21]: https://github.com/AdguardTeam/AdGuardHome/milestone/57?closed=1
-->
## [v0.107.20] - 2022-12-07
See also the [v0.107.20 GitHub milestone][ms-v0.107.20].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2022-41717 and CVE-2022-41720 Go vulnerabilities fixed in [Go
1.18.9][go-1.18.9].
### Added
- The ability to clear the DNS cache ([#5190]).
### Changed
- DHCP server initialization errors are now logged at debug level if the server
itself disabled ([#4944]).
### Fixed
- Wrong validation error messages on the DHCP configuration page ([#5208]).
- Slow upstream checks making the API unresponsive ([#5193]).
- The TLS initialization errors preventing AdGuard Home from starting ([#5189]).
Instead, AdGuard Home disables encryption and shows an error message on the
encryption settings page in the UI, which was the intended previous behavior.
- URLs of some vetted blocklists.
[#4944]: https://github.com/AdguardTeam/AdGuardHome/issues/4944
[#5189]: https://github.com/AdguardTeam/AdGuardHome/issues/5189
[#5190]: https://github.com/AdguardTeam/AdGuardHome/issues/5190
[#5193]: https://github.com/AdguardTeam/AdGuardHome/issues/5193
[#5208]: https://github.com/AdguardTeam/AdGuardHome/issues/5208
[go-1.18.9]: https://groups.google.com/g/golang-announce/c/L_3rmdT0BMU
[ms-v0.107.20]: https://github.com/AdguardTeam/AdGuardHome/milestone/56?closed=1
## [v0.107.19] - 2022-11-23
See also the [v0.107.19 GitHub milestone][ms-v0.107.19]. See also the [v0.107.19 GitHub milestone][ms-v0.107.19].
[ms-v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/milestone/55?closed=1
-->
### Added ### Added
- The ability to block popular Mastodon instances
([AdguardTeam/HostlistsRegistry#100]).
- The new `--update` command-line option, which allows updating AdGuard Home - The new `--update` command-line option, which allows updating AdGuard Home
silently ([#4223]). silently ([#4223]).
### Changed
- Minor UI changes.
[#4223]: https://github.com/AdguardTeam/AdGuardHome/issues/4223 [#4223]: https://github.com/AdguardTeam/AdGuardHome/issues/4223
[ms-v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/milestone/55?closed=1
[AdguardTeam/HostlistsRegistry#100]: https://github.com/AdguardTeam/HostlistsRegistry/pull/100
## [v0.107.18] - 2022-11-08 ## [v0.107.18] - 2022-11-08
@@ -1482,13 +1427,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.21...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.19...HEAD
[v0.107.21]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.20...v0.107.21 [v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...v0.107.19
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.20...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...HEAD
[v0.107.20]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.19...v0.107.20
[v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...v0.107.19
[v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18 [v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17 [v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17
[v0.107.16]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...v0.107.16 [v0.107.16]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...v0.107.16

View File

@@ -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:5.4' 'dockerGo': 'adguard/golang-ubuntu:5.3'
'stages': 'stages':
- 'Build frontend': - 'Build frontend':
@@ -322,7 +322,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:5.4' 'dockerGo': 'adguard/golang-ubuntu:5.3'
# release-vX.Y.Z branches are the branches from which the actual final release # release-vX.Y.Z branches are the branches from which the actual final release
# is built. # is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+': - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -337,4 +337,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:5.4' 'dockerGo': 'adguard/golang-ubuntu:5.3'

View File

@@ -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:5.4' 'dockerGo': 'adguard/golang-ubuntu:5.3'
'stages': 'stages':
- 'Tests': - 'Tests':

View File

@@ -392,7 +392,6 @@
"encryption_issuer": "المصدر", "encryption_issuer": "المصدر",
"encryption_hostnames": "اسم المستضيف", "encryption_hostnames": "اسم المستضيف",
"encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟", "encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟",
"encryption_warning": "تحذير",
"topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير</0>.", "topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير</0>.",
"topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير</0>.", "topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير</0>.",
"form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535", "form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Выдавец", "encryption_issuer": "Выдавец",
"encryption_hostnames": "Імёны хастоў", "encryption_hostnames": "Імёны хастоў",
"encryption_reset": "Вы ўпэўнены, што хочаце скінуць налады шыфравання?", "encryption_reset": "Вы ўпэўнены, што хочаце скінуць налады шыфравання?",
"encryption_warning": "Папярэджанне",
"topline_expiring_certificate": "Ваш SSL-сертыфікат хутка мінае. Абновіце <0>Налады шыфравання</0>.", "topline_expiring_certificate": "Ваш SSL-сертыфікат хутка мінае. Абновіце <0>Налады шыфравання</0>.",
"topline_expired_certificate": "Ваш SSL-сертыфікат мінуў. Абновіце <0>Налады шыфравання</0>.", "topline_expired_certificate": "Ваш SSL-сертыфікат мінуў. Абновіце <0>Налады шыфравання</0>.",
"form_error_port_range": "Увядзіце нумар порта з інтэрвалу 80-65535", "form_error_port_range": "Увядзіце нумар порта з інтэрвалу 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Бяспечны інтэрнэт", "safe_browsing": "Бяспечны інтэрнэт",
"served_from_cache": "{{value}} <i>(атрымана з кэша)</i>", "served_from_cache": "{{value}} <i>(атрымана з кэша)</i>",
"form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў", "form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў",
"anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яго ў <1>Агульных наладах</1> .", "anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яго ў <1>Агульных наладах</1> ."
"confirm_dns_cache_clear": "Вы ўпэўнены, што хочаце ачысціць кэш DNS?",
"cache_cleared": "Кэш DNS паспяхова ачышчаны",
"clear_cache": "Ачысціць кэш"
} }

View File

@@ -244,7 +244,6 @@
"encryption_issuer": "Изпълнител", "encryption_issuer": "Изпълнител",
"encryption_hostnames": "Имена на хоста", "encryption_hostnames": "Имена на хоста",
"encryption_reset": "Сигурни ли сте че искате да изтриете настройките за криптиране?", "encryption_reset": "Сигурни ли сте че искате да изтриете настройките за криптиране?",
"encryption_warning": "Внимание",
"topline_expiring_certificate": "Вашият SSL сертификат изтича. Обнови <0>Настройки за криптиране</0>.", "topline_expiring_certificate": "Вашият SSL сертификат изтича. Обнови <0>Настройки за криптиране</0>.",
"topline_expired_certificate": "Вашият SSL сертификат е изтекъл. Обнови <0>Настройки за криптиране</0>.", "topline_expired_certificate": "Вашият SSL сертификат е изтекъл. Обнови <0>Настройки за криптиране</0>.",
"form_error_port_range": "Въведете порт в диапазона 80-65535", "form_error_port_range": "Въведете порт в диапазона 80-65535",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Vydavatel", "encryption_issuer": "Vydavatel",
"encryption_hostnames": "Názvy hostitelů", "encryption_hostnames": "Názvy hostitelů",
"encryption_reset": "Opravdu chcete obnovit nastavení šifrování?", "encryption_reset": "Opravdu chcete obnovit nastavení šifrování?",
"encryption_warning": "Varování",
"topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování</0>.", "topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování</0>.",
"topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování</0>.", "topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování</0>.",
"form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535", "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Bezpečné prohlížení", "safe_browsing": "Bezpečné prohlížení",
"served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>", "served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
"form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé", "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizace IP je zapnuta. Můžete ji vypnout v <1>Obecných nastaveních</1>.", "anonymizer_notification": "<0>Poznámka:</0> Anonymizace IP je zapnuta. Můžete ji vypnout v <1>Obecných nastaveních</1>."
"confirm_dns_cache_clear": "Opravdu chcete vymazat mezipaměť DNS?",
"cache_cleared": "Mezipaměť DNS úspěšně vymazána",
"clear_cache": "Vymazat mezipaměť"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Udsteder", "encryption_issuer": "Udsteder",
"encryption_hostnames": "Værtsnavne", "encryption_hostnames": "Værtsnavne",
"encryption_reset": "Sikker på, at du vil nulstille krypteringsindstillingerne?", "encryption_reset": "Sikker på, at du vil nulstille krypteringsindstillingerne?",
"encryption_warning": "Advarsel",
"topline_expiring_certificate": "Dit SSL-certifikat er ved at udløbe. Opdatér <0>Krypteringsindstillinger</0>.", "topline_expiring_certificate": "Dit SSL-certifikat er ved at udløbe. Opdatér <0>Krypteringsindstillinger</0>.",
"topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.", "topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.",
"form_error_port_range": "Angiv portnummer i intervallet 80-65535", "form_error_port_range": "Angiv portnummer i intervallet 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Sikker Browsing", "safe_browsing": "Sikker Browsing",
"served_from_cache": "{{value}} <i>(leveret fra cache)</i>", "served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
"form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn.", "form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn.",
"anonymizer_notification": "<0>Bemærk:</0> IP-anonymisering er aktiveret. Det kan deaktiveres via <1>Generelle indstillinger</1>.", "anonymizer_notification": "<0>Bemærk:</0> IP-anonymisering er aktiveret. Det kan deaktiveres via <1>Generelle indstillinger</1>."
"confirm_dns_cache_clear": "Sikker på, at DNS-cache skal ryddes?",
"cache_cleared": "DNS-cache hermed ryddet",
"clear_cache": "Ryd cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Ausgestellt von", "encryption_issuer": "Ausgestellt von",
"encryption_hostnames": "Hostnamen", "encryption_hostnames": "Hostnamen",
"encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?", "encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?",
"encryption_warning": "Warnhinweis",
"topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.", "topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
"topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.", "topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
"form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein", "form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein",
@@ -638,8 +637,5 @@
"safe_browsing": "Internetsicherheit", "safe_browsing": "Internetsicherheit",
"served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>", "served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
"form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten", "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten",
"anonymizer_notification": "<0>Hinweis:</0> Die IP-Anonymisierung ist aktiviert. Sie können sie in den <1>Allgemeinen Einstellungen</1> deaktivieren.", "anonymizer_notification": "<0>Hinweis:</0> Die IP-Anonymisierung ist aktiviert. Sie können sie in den <1>Allgemeinen Einstellungen</1> deaktivieren."
"confirm_dns_cache_clear": "Möchten Sie den DNS-Cache wirklich leeren?",
"cache_cleared": "DNS-Cache erfolgreich geleert",
"clear_cache": "Cache leeren"
} }

View File

@@ -37,6 +37,8 @@
"dhcp_ipv6_settings": "DHCP IPv6 Settings", "dhcp_ipv6_settings": "DHCP IPv6 Settings",
"form_error_required": "Required field", "form_error_required": "Required field",
"form_error_ip4_format": "Invalid IPv4 address", "form_error_ip4_format": "Invalid IPv4 address",
"form_error_ip4_range_start_format": "Invalid IPv4 address of the range start",
"form_error_ip4_range_end_format": "Invalid IPv4 address of the range end",
"form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway", "form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway",
"form_error_ip6_format": "Invalid IPv6 address", "form_error_ip6_format": "Invalid IPv6 address",
"form_error_ip_format": "Invalid IP address", "form_error_ip_format": "Invalid IP address",
@@ -49,8 +51,9 @@
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"", "out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
"lower_range_start_error": "Must be lower than range start", "lower_range_start_error": "Must be lower than range start",
"greater_range_start_error": "Must be greater than range start", "greater_range_start_error": "Must be greater than range start",
"greater_range_end_error": "Must be greater than range end",
"subnet_error": "Addresses must be in one subnet", "subnet_error": "Addresses must be in one subnet",
"gateway_or_subnet_invalid": "Invalid subnet mask", "gateway_or_subnet_invalid": "Subnet mask invalid",
"dhcp_form_gateway_input": "Gateway IP", "dhcp_form_gateway_input": "Gateway IP",
"dhcp_form_subnet_input": "Subnet mask", "dhcp_form_subnet_input": "Subnet mask",
"dhcp_form_range_title": "Range of IP addresses", "dhcp_form_range_title": "Range of IP addresses",
@@ -635,8 +638,5 @@
"safe_browsing": "Safe Browsing", "safe_browsing": "Safe Browsing",
"served_from_cache": "{{value}} <i>(served from cache)</i>", "served_from_cache": "{{value}} <i>(served from cache)</i>",
"form_error_password_length": "Password must be at least {{value}} characters long", "form_error_password_length": "Password must be at least {{value}} characters long",
"anonymizer_notification": "<0>Note:</0> IP anonymization is enabled. You can disable it in <1>General settings</1>.", "anonymizer_notification": "<0>Note:</0> IP anonymization is enabled. You can disable it in <1>General settings</1>."
"confirm_dns_cache_clear": "Are you sure you want to clear DNS cache?",
"cache_cleared": "DNS cache successfully cleared",
"clear_cache": "Clear cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Emisor", "encryption_issuer": "Emisor",
"encryption_hostnames": "Nombres de hosts", "encryption_hostnames": "Nombres de hosts",
"encryption_reset": "¿Estás seguro de que deseas restablecer la configuración de cifrado?", "encryption_reset": "¿Estás seguro de que deseas restablecer la configuración de cifrado?",
"encryption_warning": "Advertencia",
"topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado</0>.", "topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado</0>.",
"topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado</0>.", "topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado</0>.",
"form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535", "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navegación segura", "safe_browsing": "Navegación segura",
"served_from_cache": "{{value}} <i>(servido desde la caché)</i>", "served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
"form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres", "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres",
"anonymizer_notification": "<0>Nota:</0> La anonimización de IP está habilitada. Puedes deshabilitarla en <1>Configuración general</1>.", "anonymizer_notification": "<0>Nota:</0> La anonimización de IP está habilitada. Puedes deshabilitarla en <1>Configuración general</1>."
"confirm_dns_cache_clear": "¿Estás seguro de que deseas borrar la caché de DNS?",
"cache_cleared": "Caché DNS borrado con éxito",
"clear_cache": "Borrar caché"
} }

View File

@@ -361,7 +361,6 @@
"encryption_issuer": "صادر کننده", "encryption_issuer": "صادر کننده",
"encryption_hostnames": "نام میزبان", "encryption_hostnames": "نام میزبان",
"encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟", "encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟",
"encryption_warning": "هشدار",
"topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.", "topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
"topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.", "topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
"form_error_port_range": "مقدار پورت را در محدوده 80-65535 وارد کنید", "form_error_port_range": "مقدار پورت را در محدوده 80-65535 وارد کنید",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Toimittaja", "encryption_issuer": "Toimittaja",
"encryption_hostnames": "Isäntänimet", "encryption_hostnames": "Isäntänimet",
"encryption_reset": "Haluatko varmasti palauttaa salausasetukset?", "encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
"encryption_warning": "Varoitus",
"topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.", "topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
"topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.", "topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
"form_error_port_range": "Syötä portti väliltä 80-65535", "form_error_port_range": "Syötä portti väliltä 80-65535",
@@ -543,8 +542,8 @@
"descr": "Kuvaus", "descr": "Kuvaus",
"whois": "WHOIS", "whois": "WHOIS",
"filtering_rules_learn_more": "<0>Lue lisää</0> omien hosts-listojesi luonnista.", "filtering_rules_learn_more": "<0>Lue lisää</0> omien hosts-listojesi luonnista.",
"blocked_by_response": "Estetty vastauksen CNAME:n tai IP:n perusteella", "blocked_by_response": "Vastauksen sisältämän CNAME:n tai IP:n estämä",
"blocked_by_cname_or_ip": "Estetty CNAME:n tai IP:n perusteella", "blocked_by_cname_or_ip": "CNAME:n tai IP:n estämä",
"try_again": "Yritä uudelleen", "try_again": "Yritä uudelleen",
"domain_desc": "Syötä korvattava verkkotunnus tai jokerimerkki.", "domain_desc": "Syötä korvattava verkkotunnus tai jokerimerkki.",
"example_rewrite_domain": "korvaa vain tämän verkkotunnuksen vastaukset", "example_rewrite_domain": "korvaa vain tämän verkkotunnuksen vastaukset",
@@ -638,8 +637,5 @@
"safe_browsing": "Turvallinen selaus", "safe_browsing": "Turvallinen selaus",
"served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>", "served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
"form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä", "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä",
"anonymizer_notification": "<0>Huomioi:</0> IP-osoitteen anonymisointi on käytössä. Voit poistaa sen käytöstä <1>Yleisistä asetuksista</1>.", "anonymizer_notification": "<0>Huomioi:</0> IP-osoitteen anonymisointi on käytössä. Voit poistaa sen käytöstä <1>Yleisistä asetuksista</1>."
"confirm_dns_cache_clear": "Haluatko varmasti tyhjentää DNS-välimuistin?",
"cache_cleared": "DNS-välimuistin tyhjennys onnistui",
"clear_cache": "Tyhjennä välimuisti"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Émetteur", "encryption_issuer": "Émetteur",
"encryption_hostnames": "Noms d'hôte", "encryption_hostnames": "Noms d'hôte",
"encryption_reset": "Voulez-vous vraiment réinitialiser les paramètres de chiffrement ?", "encryption_reset": "Voulez-vous vraiment réinitialiser les paramètres de chiffrement ?",
"encryption_warning": "Attention",
"topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement</0>.", "topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
"topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement</0>.", "topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
"form_error_port_range": "Saisissez une valeur de port entre 80 et 65535", "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navigation sécurisée", "safe_browsing": "Navigation sécurisée",
"served_from_cache": "{{value}} <i>(depuis le cache)</i>", "served_from_cache": "{{value}} <i>(depuis le cache)</i>",
"form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères", "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères",
"anonymizer_notification": "<0>Note :</0> L'anonymisation IP est activée. Vous pouvez la désactiver dans les <1>paramètres généraux</1>.", "anonymizer_notification": "<0>Note :</0> L'anonymisation IP est activée. Vous pouvez la désactiver dans les <1>paramètres généraux</1>."
"confirm_dns_cache_clear": "Voulez-vous vraiment vider le cache DNS ?",
"cache_cleared": "Le cache DNS a été vidé",
"clear_cache": "Vider le cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Izdavač", "encryption_issuer": "Izdavač",
"encryption_hostnames": "Nazivi računala", "encryption_hostnames": "Nazivi računala",
"encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?", "encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?",
"encryption_warning": "Upozorenje",
"topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
"topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
"form_error_port_range": "Unesite broj porta od 80 do 65536", "form_error_port_range": "Unesite broj porta od 80 do 65536",
@@ -638,8 +637,5 @@
"safe_browsing": "Sigurno surfanje", "safe_browsing": "Sigurno surfanje",
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>", "served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova", "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.", "anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>."
"confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?",
"cache_cleared": "DNS predmemorija je uspješno izbrisana",
"clear_cache": "Očisti predmemoriju"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Kibocsátó", "encryption_issuer": "Kibocsátó",
"encryption_hostnames": "Hosztnevek", "encryption_hostnames": "Hosztnevek",
"encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?", "encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
"encryption_warning": "Figyelmeztetés",
"topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.", "topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
"topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.", "topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
"form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül", "form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül",
@@ -638,8 +637,5 @@
"safe_browsing": "Biztonságos böngészés", "safe_browsing": "Biztonságos böngészés",
"served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>", "served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>",
"form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen", "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen",
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .", "anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> ."
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
"clear_cache": "Gyorsítótár törlése"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Penerbit", "encryption_issuer": "Penerbit",
"encryption_hostnames": "Nama host", "encryption_hostnames": "Nama host",
"encryption_reset": "Anda yakin ingin mengatur ulang pengaturan enkripsi?", "encryption_reset": "Anda yakin ingin mengatur ulang pengaturan enkripsi?",
"encryption_warning": "Perhatian",
"topline_expiring_certificate": "Sertifikat SSL Anda hampir kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.", "topline_expiring_certificate": "Sertifikat SSL Anda hampir kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"topline_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.", "topline_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"form_error_port_range": "Masukkan nomor port di kisaran 80-65535", "form_error_port_range": "Masukkan nomor port di kisaran 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Penjelajahan Aman", "safe_browsing": "Penjelajahan Aman",
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>", "served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
"form_error_password_length": "Kata sandi harus minimal {{value}} karakter", "form_error_password_length": "Kata sandi harus minimal {{value}} karakter",
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .", "anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> ."
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
"cache_cleared": "Cache DNS berhasil dibersihkan",
"clear_cache": "Hapus cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Emittente", "encryption_issuer": "Emittente",
"encryption_hostnames": "Nomi host", "encryption_hostnames": "Nomi host",
"encryption_reset": "Sei sicuro di voler ripristinare le impostazioni di crittografia?", "encryption_reset": "Sei sicuro di voler ripristinare le impostazioni di crittografia?",
"encryption_warning": "Attenzione",
"topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia </ 0>.", "topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia </ 0>.",
"topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia </ 0>.", "topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia </ 0>.",
"form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535", "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navigazione Sicura", "safe_browsing": "Navigazione Sicura",
"served_from_cache": "{{value}} <i>(fornito dalla cache)</i>", "served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
"form_error_password_length": "La password deve contenere almeno {{value}} caratteri", "form_error_password_length": "La password deve contenere almeno {{value}} caratteri",
"anonymizer_notification": "<0>Attenzione:</0> L'anonimizzazione dell'IP è abilitata. Puoi disabilitarla in <1>Impostazioni generali</1>.", "anonymizer_notification": "<0>Attenzione:</0> L'anonimizzazione dell'IP è abilitata. Puoi disabilitarla in <1>Impostazioni generali</1>."
"confirm_dns_cache_clear": "Sei sicuro di voler cancellare la cache DNS?",
"cache_cleared": "Cache DNS è stata cancellata correttamente",
"clear_cache": "Cancella cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "発行者", "encryption_issuer": "発行者",
"encryption_hostnames": "ホスト名", "encryption_hostnames": "ホスト名",
"encryption_reset": "暗号化設定をリセットして良いですか?", "encryption_reset": "暗号化設定をリセットして良いですか?",
"encryption_warning": "警告",
"topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。", "topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
"topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。", "topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
"form_error_port_range": "80〜65535 の範囲内でポート番号を入力してください", "form_error_port_range": "80〜65535 の範囲内でポート番号を入力してください",
@@ -638,8 +637,5 @@
"safe_browsing": "セーフブラウジング", "safe_browsing": "セーフブラウジング",
"served_from_cache": "{{value}} <i>(キャッシュから応答)</i>", "served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
"form_error_password_length": "パスワードは{{value}}文字以上にしてください", "form_error_password_length": "パスワードは{{value}}文字以上にしてください",
"anonymizer_notification": "【<0>注意</0>】IPの匿名化が有効になっています。 <1>一般設定</1>で無効にできます。", "anonymizer_notification": "【<0>注意</0>】IPの匿名化が有効になっています。 <1>一般設定</1>で無効にできます。"
"confirm_dns_cache_clear": "DNS キャッシュをクリアしてもよろしいですか?",
"cache_cleared": "DNSキャッシュのクリア完了です。",
"clear_cache": "キャッシュをクリアする"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "발행자", "encryption_issuer": "발행자",
"encryption_hostnames": "호스트 이름", "encryption_hostnames": "호스트 이름",
"encryption_reset": "암호화 설정을 재설정하시겠습니까?", "encryption_reset": "암호화 설정을 재설정하시겠습니까?",
"encryption_warning": "경고",
"topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.", "topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
"topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.", "topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
"form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요", "form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요",
@@ -638,8 +637,5 @@
"safe_browsing": "세이프 브라우징", "safe_browsing": "세이프 브라우징",
"served_from_cache": "{{value}} <i>(캐시에서 제공)</i>", "served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
"form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다", "form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다",
"anonymizer_notification": "<0>참고:</0> IP 익명화가 활성화되었습니다. <1>일반 설정</1>에서 비활성화할 수 있습니다.", "anonymizer_notification": "<0>참고:</0> IP 익명화가 활성화되었습니다. <1>일반 설정</1>에서 비활성화할 수 있습니다."
"confirm_dns_cache_clear": "정말로 DNS 캐시를 지우시겠습니까?",
"cache_cleared": "DNS 캐시를 성공적으로 지웠습니다",
"clear_cache": "캐시 지우기"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Uitgever", "encryption_issuer": "Uitgever",
"encryption_hostnames": "Hostnamen", "encryption_hostnames": "Hostnamen",
"encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?", "encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
"encryption_warning": "Waarschuwing",
"topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.", "topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.",
"topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.", "topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
"form_error_port_range": "Poortnummer invoeren tussen 80 en 65535", "form_error_port_range": "Poortnummer invoeren tussen 80 en 65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Veilig browsen", "safe_browsing": "Veilig browsen",
"served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>", "served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
"form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn", "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn",
"anonymizer_notification": "<0>Opmerking:</0> IP-anonimisering is ingeschakeld. Je kunt het uitschakelen in <1>Algemene instellingen</1>.", "anonymizer_notification": "<0>Opmerking:</0> IP-anonimisering is ingeschakeld. Je kunt het uitschakelen in <1>Algemene instellingen</1>."
"confirm_dns_cache_clear": "Weet je zeker dat je de DNS-cache wilt wissen?",
"cache_cleared": "DNS-cache succesvol gewist",
"clear_cache": "Cache wissen"
} }

View File

@@ -373,7 +373,6 @@
"encryption_issuer": "Utsteder", "encryption_issuer": "Utsteder",
"encryption_hostnames": "Vertsnavn", "encryption_hostnames": "Vertsnavn",
"encryption_reset": "Er du sikker på at du vil tilbakestille krypteringsinnstillingene?", "encryption_reset": "Er du sikker på at du vil tilbakestille krypteringsinnstillingene?",
"encryption_warning": "Advarsel",
"topline_expiring_certificate": "Ditt SSL-sertifikat er i ferd med å utløpe. Oppdater <0>Krypteringsinnstillinger</0>.", "topline_expiring_certificate": "Ditt SSL-sertifikat er i ferd med å utløpe. Oppdater <0>Krypteringsinnstillinger</0>.",
"topline_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger</0>.", "topline_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger</0>.",
"form_error_port_range": "Skriv inn et portnummer i området 80-65535", "form_error_port_range": "Skriv inn et portnummer i området 80-65535",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Zgłaszający", "encryption_issuer": "Zgłaszający",
"encryption_hostnames": "Nazwy hostów", "encryption_hostnames": "Nazwy hostów",
"encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?", "encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
"encryption_warning": "Uwaga!",
"topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.", "topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.", "topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"form_error_port_range": "Wpisz numer portu z zakresu 80-65535", "form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Bezpieczne przeglądanie", "safe_browsing": "Bezpieczne przeglądanie",
"served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>", "served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>",
"form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków", "form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków",
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.", "anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>."
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
"cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona",
"clear_cache": "Wyczyść pamięć podręczną"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Emissor", "encryption_issuer": "Emissor",
"encryption_hostnames": "Nomes dos servidores", "encryption_hostnames": "Nomes dos servidores",
"encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?", "encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?",
"encryption_warning": "Aviso",
"topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>", "topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>",
"topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>", "topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>",
"form_error_port_range": "Digite um número de porta entre 80 e 65535", "form_error_port_range": "Digite um número de porta entre 80 e 65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navegação segura", "safe_browsing": "Navegação segura",
"served_from_cache": "{{value}} <i>(servido do cache)</i>", "served_from_cache": "{{value}} <i>(servido do cache)</i>",
"form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres", "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-lo em <1>Configurações gerais</1>.", "anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-lo em <1>Configurações gerais</1>."
"confirm_dns_cache_clear": "Tem certeza de que deseja limpar o cache DNS?",
"cache_cleared": "Cache DNS limpo com sucesso",
"clear_cache": "Limpar cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Emissor", "encryption_issuer": "Emissor",
"encryption_hostnames": "Nomes dos servidores", "encryption_hostnames": "Nomes dos servidores",
"encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?", "encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?",
"encryption_warning": "Aviso",
"topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia</0>.", "topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia</0>.",
"topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia</0>.", "topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia</0>.",
"form_error_port_range": "Digite um numero de porta entre 80 e 65535", "form_error_port_range": "Digite um numero de porta entre 80 e 65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navegação segura", "safe_browsing": "Navegação segura",
"served_from_cache": "{{value}} <i>(servido do cache)</i>", "served_from_cache": "{{value}} <i>(servido do cache)</i>",
"form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres", "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-la em <1>Definições gerais</1>.", "anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-la em <1>Definições gerais</1>."
"confirm_dns_cache_clear": "Tem certeza de que quer limpar a cache DNS?",
"cache_cleared": "O cache DNS foi apagado com sucesso",
"clear_cache": "Limpar cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Emitent", "encryption_issuer": "Emitent",
"encryption_hostnames": "Nume de host", "encryption_hostnames": "Nume de host",
"encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?", "encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?",
"encryption_warning": "Avertisment",
"topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare</0>.", "topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare</0>.",
"topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare</0>.", "topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare</0>.",
"form_error_port_range": "Introduceți valoarea portului între 80-65535", "form_error_port_range": "Introduceți valoarea portului între 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Navigare în siguranță", "safe_browsing": "Navigare în siguranță",
"served_from_cache": "{{value}} <i>(furnizat din cache)</i>", "served_from_cache": "{{value}} <i>(furnizat din cache)</i>",
"form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere", "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere",
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.", "anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>."
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
"cache_cleared": "Cache-ul DNS a fost golit cu succes",
"clear_cache": "Goliți memoria cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Издатель", "encryption_issuer": "Издатель",
"encryption_hostnames": "Имена хостов", "encryption_hostnames": "Имена хостов",
"encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?", "encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
"encryption_warning": "Предупреждение",
"topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.", "topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
"topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.", "topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.",
"form_error_port_range": "Введите номер порта из интервала 80-65535", "form_error_port_range": "Введите номер порта из интервала 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Безопасный интернет", "safe_browsing": "Безопасный интернет",
"served_from_cache": "{{value}} <i>(получено из кеша)</i>", "served_from_cache": "{{value}} <i>(получено из кеша)</i>",
"form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов", "form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов",
"anonymizer_notification": "<0>Внимание:</0> включена анонимизация IP-адресов. Вы можете отключить её в разделе <1>Основные настройки</1>.", "anonymizer_notification": "<0>Внимание:</0> включена анонимизация IP-адресов. Вы можете отключить её в разделе <1>Основные настройки</1>."
"confirm_dns_cache_clear": "Вы уверены, что хотите очистить кеш DNS?",
"cache_cleared": "Кеш DNS успешно очищен",
"clear_cache": "Очистить кеш"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Vydavateľ", "encryption_issuer": "Vydavateľ",
"encryption_hostnames": "Názvy hostiteľov", "encryption_hostnames": "Názvy hostiteľov",
"encryption_reset": "Naozaj chcete obnoviť nastavenia šifrovania?", "encryption_reset": "Naozaj chcete obnoviť nastavenia šifrovania?",
"encryption_warning": "Varovanie",
"topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania</0>.", "topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania</0>.",
"topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.", "topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.",
"form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535", "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Bezpečné prehliadanie", "safe_browsing": "Bezpečné prehliadanie",
"served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>", "served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
"form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov", "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizácia IP je zapnutá. Môžete ju vypnúť vo <1>Všeobecných nastaveniach</1>.", "anonymizer_notification": "<0>Poznámka:</0> Anonymizácia IP je zapnutá. Môžete ju vypnúť vo <1>Všeobecných nastaveniach</1>."
"confirm_dns_cache_clear": "Naozaj chcete vymazať vyrovnávaciu pamäť DNS?",
"cache_cleared": "Vyrovnávacia pamäť DNS bola úspešne vymazaná",
"clear_cache": "Vymazať vyrovnávaciu pamäť"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Izdajatelj", "encryption_issuer": "Izdajatelj",
"encryption_hostnames": "Imena gostiteljev", "encryption_hostnames": "Imena gostiteljev",
"encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?", "encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?",
"encryption_warning": "Opozorilo",
"topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja</0>.", "topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja</0>.",
"topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja</0>.", "topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja</0>.",
"form_error_port_range": "Vnesite številko vrat v razponu med 80-65535", "form_error_port_range": "Vnesite številko vrat v razponu med 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Varno brskanje", "safe_browsing": "Varno brskanje",
"served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>", "served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
"form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov", "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov",
"anonymizer_notification": "<0>Opomba:</0> Anonimizacija IP je omogočena. Onemogočite ga lahko v <1>Splošnih nastavitvah</1>.", "anonymizer_notification": "<0>Opomba:</0> Anonimizacija IP je omogočena. Onemogočite ga lahko v <1>Splošnih nastavitvah</1>."
"confirm_dns_cache_clear": "Ali ste prepričani, da želite počistiti predpomnilnik DNS?",
"cache_cleared": "Predpomnilnik DNS je bil uspešno počiščen",
"clear_cache": "Počisti predpomnilnik"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Izdavač", "encryption_issuer": "Izdavač",
"encryption_hostnames": "Imena hostova", "encryption_hostnames": "Imena hostova",
"encryption_reset": "Jeste li sigurni da želite dda resetujete postavke šifrovanja?", "encryption_reset": "Jeste li sigurni da želite dda resetujete postavke šifrovanja?",
"encryption_warning": "Upozorenje",
"topline_expiring_certificate": "Vaš SSL sertifikat uskoro ističe. Ažurirajte <0>postavke šifrovanja</0>.", "topline_expiring_certificate": "Vaš SSL sertifikat uskoro ističe. Ažurirajte <0>postavke šifrovanja</0>.",
"topline_expired_certificate": "Vaš SSL sertifikat je istekao. Ažurirajte <0>postavke šifrovanja</0>.", "topline_expired_certificate": "Vaš SSL sertifikat je istekao. Ažurirajte <0>postavke šifrovanja</0>.",
"form_error_port_range": "Unesite vrednost porta u opsegu od 80-65535", "form_error_port_range": "Unesite vrednost porta u opsegu od 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Sigurno pregledanje", "safe_browsing": "Sigurno pregledanje",
"served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>", "served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova", "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova",
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.", "anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>."
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
"cache_cleared": "DNS keš je uspešno očišćen",
"clear_cache": "Obriši keš memoriju"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Utgivare", "encryption_issuer": "Utgivare",
"encryption_hostnames": "Värdnamn", "encryption_hostnames": "Värdnamn",
"encryption_reset": "Är du säker på att du vill återställa krypteringsinställningarna?", "encryption_reset": "Är du säker på att du vill återställa krypteringsinställningarna?",
"encryption_warning": "Varning",
"topline_expiring_certificate": "Ditt SSL-certifikat håller på att gå ut. <0>Krypteringsinställningar</0>.", "topline_expiring_certificate": "Ditt SSL-certifikat håller på att gå ut. <0>Krypteringsinställningar</0>.",
"topline_expired_certificate": "Ditt SSL-certifikat har gått ut. Uppdatera <0>Krypteringsinställningar</0>-", "topline_expired_certificate": "Ditt SSL-certifikat har gått ut. Uppdatera <0>Krypteringsinställningar</0>-",
"form_error_port_range": "Ange ett portnummer inom värdena 80-65535", "form_error_port_range": "Ange ett portnummer inom värdena 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Säker surfning", "safe_browsing": "Säker surfning",
"served_from_cache": "{{value}} <i>(levereras från cache)</i>", "served_from_cache": "{{value}} <i>(levereras från cache)</i>",
"form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt", "form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt",
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.", "anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>."
"confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?",
"cache_cleared": "DNS-cacheminnet har rensats",
"clear_cache": "Rensa cache"
} }

View File

@@ -262,7 +262,6 @@
"encryption_issuer": "ผู้ออกใบรับรอง:", "encryption_issuer": "ผู้ออกใบรับรอง:",
"encryption_hostnames": "ชื่อโฮส", "encryption_hostnames": "ชื่อโฮส",
"encryption_reset": "คุณแน่ใจนะว่าจะล้างค่าการเข้ารหัส?", "encryption_reset": "คุณแน่ใจนะว่าจะล้างค่าการเข้ารหัส?",
"encryption_warning": "คำเตือน",
"topline_expiring_certificate": "ใบรับรอง SSL ของคุณกำลังจะหมดอายุ กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.", "topline_expiring_certificate": "ใบรับรอง SSL ของคุณกำลังจะหมดอายุ กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
"topline_expired_certificate": "ใบรับรอง SSL ของคุณหมดอายุแล้ว กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.", "topline_expired_certificate": "ใบรับรอง SSL ของคุณหมดอายุแล้ว กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
"form_error_port_unsafe": "เป็นพอร์ทที่ไม่ปลอดภัย", "form_error_port_unsafe": "เป็นพอร์ทที่ไม่ปลอดภัย",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Sağlayan", "encryption_issuer": "Sağlayan",
"encryption_hostnames": "Ana makine adları", "encryption_hostnames": "Ana makine adları",
"encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?", "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
"encryption_warning": "Uyarı",
"topline_expiring_certificate": "SSL sertifikanızın süresi sona üzere. <0>Şifreleme ayarlarını</0> güncelleyin.", "topline_expiring_certificate": "SSL sertifikanızın süresi sona üzere. <0>Şifreleme ayarlarını</0> güncelleyin.",
"topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.", "topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
"form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin", "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin",
@@ -638,8 +637,5 @@
"safe_browsing": "Güvenli Gezinti", "safe_browsing": "Güvenli Gezinti",
"served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>", "served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
"form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır", "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır",
"anonymizer_notification": "<0>Not:</0> IP anonimleştirme etkinleştirildi. Bunu <1>Genel ayarlardan</1> devre dışı bırakabilirsiniz.", "anonymizer_notification": "<0>Not:</0> IP anonimleştirme etkinleştirildi. Bunu <1>Genel ayarlardan</1> devre dışı bırakabilirsiniz."
"confirm_dns_cache_clear": "DNS önbelleğini temizlemek istediğinizden emin misiniz?",
"cache_cleared": "DNS önbelleği başarıyla temizlendi",
"clear_cache": "Önbelleği temizle"
} }

View File

@@ -371,7 +371,7 @@
"encryption_redirect": "Автоматично перенаправляти на HTTPS", "encryption_redirect": "Автоматично перенаправляти на HTTPS",
"encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.", "encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.",
"encryption_https": "Порт HTTPS", "encryption_https": "Порт HTTPS",
"encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також сервер DNS-over-HTTPS буде доступний за адресою '/dns-query'.", "encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.",
"encryption_dot": "Порт DNS-over-TLS", "encryption_dot": "Порт DNS-over-TLS",
"encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.", "encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.",
"encryption_doq": "Порт DNS-over-QUIC", "encryption_doq": "Порт DNS-over-QUIC",
@@ -393,7 +393,6 @@
"encryption_issuer": "Видавець", "encryption_issuer": "Видавець",
"encryption_hostnames": "Назви вузлів", "encryption_hostnames": "Назви вузлів",
"encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?", "encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
"encryption_warning": "Попередження",
"topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.", "topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
"topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.", "topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
"form_error_port_range": "Введіть значення порту в діапазоні 8065535", "form_error_port_range": "Введіть значення порту в діапазоні 8065535",
@@ -638,8 +637,5 @@
"safe_browsing": "Безпечний перегляд", "safe_browsing": "Безпечний перегляд",
"served_from_cache": "{{value}} <i>(отримано з кешу)</i>", "served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
"form_error_password_length": "Пароль мусить мати принаймні {{value}} символів", "form_error_password_length": "Пароль мусить мати принаймні {{value}} символів",
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> .", "anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> ."
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
"cache_cleared": "Кеш DNS успішно очищено",
"clear_cache": "Очистити кеш"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "Phát hành", "encryption_issuer": "Phát hành",
"encryption_hostnames": "Tên máy chủ", "encryption_hostnames": "Tên máy chủ",
"encryption_reset": "Bạn có chắc chắn muốn đặt lại cài đặt mã hóa?", "encryption_reset": "Bạn có chắc chắn muốn đặt lại cài đặt mã hóa?",
"encryption_warning": "Cảnh báo",
"topline_expiring_certificate": "Chứng chỉ SSL của bạn sắp hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.", "topline_expiring_certificate": "Chứng chỉ SSL của bạn sắp hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.",
"topline_expired_certificate": "Chứng chỉ SSL của bạn đã hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.", "topline_expired_certificate": "Chứng chỉ SSL của bạn đã hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.",
"form_error_port_range": "Nhập giá trị cổng trong phạm vi 80-65535", "form_error_port_range": "Nhập giá trị cổng trong phạm vi 80-65535",
@@ -638,8 +637,5 @@
"safe_browsing": "Duyệt web an toàn", "safe_browsing": "Duyệt web an toàn",
"served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>", "served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>",
"form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự", "form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự",
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.", "anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>."
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
"cache_cleared": "Đã xóa thành công bộ đệm DNS",
"clear_cache": "Xóa bộ nhớ cache"
} }

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "颁发者", "encryption_issuer": "颁发者",
"encryption_hostnames": "主机名", "encryption_hostnames": "主机名",
"encryption_reset": "您确定想要重置加密设置?", "encryption_reset": "您确定想要重置加密设置?",
"encryption_warning": "警告",
"topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。", "topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
"topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。", "topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
"form_error_port_range": "输入 80 - 65535 范围内的端口值", "form_error_port_range": "输入 80 - 65535 范围内的端口值",
@@ -638,8 +637,5 @@
"safe_browsing": "安全浏览", "safe_browsing": "安全浏览",
"served_from_cache": "{{value}}<i>(由缓存提供)</i>", "served_from_cache": "{{value}}<i>(由缓存提供)</i>",
"form_error_password_length": "密码必须至少有 {{value}} 个字符", "form_error_password_length": "密码必须至少有 {{value}} 个字符",
"anonymizer_notification": "<0>注意:</0> IP 匿名化已启用。您可以在<1>常规设置</1>中禁用它。", "anonymizer_notification": "<0>注意:</0> IP 匿名化已启用。您可以在<1>常规设置</1>中禁用它。"
"confirm_dns_cache_clear": "您确定要清除 DNS 缓存吗?",
"cache_cleared": "已成功清除 DNS 缓存",
"clear_cache": "清除缓存"
} }

View File

@@ -381,7 +381,6 @@
"encryption_issuer": "簽發者", "encryption_issuer": "簽發者",
"encryption_hostnames": "主機名稱", "encryption_hostnames": "主機名稱",
"encryption_reset": "您確定要重設加密設定嗎?", "encryption_reset": "您確定要重設加密設定嗎?",
"encryption_warning": "警告",
"topline_expiring_certificate": "您的 SSL 憑證即將到期。請前往<0>加密設定</0>更新。", "topline_expiring_certificate": "您的 SSL 憑證即將到期。請前往<0>加密設定</0>更新。",
"topline_expired_certificate": "您的 SSL 憑證已到期。請前往<0>加密設定</0>更新。", "topline_expired_certificate": "您的 SSL 憑證已到期。請前往<0>加密設定</0>更新。",
"form_error_port_range": "輸入範圍 80-65535 中的值", "form_error_port_range": "輸入範圍 80-65535 中的值",

View File

@@ -393,7 +393,6 @@
"encryption_issuer": "簽發者", "encryption_issuer": "簽發者",
"encryption_hostnames": "主機名稱", "encryption_hostnames": "主機名稱",
"encryption_reset": "您確定您想要重置加密設定嗎?", "encryption_reset": "您確定您想要重置加密設定嗎?",
"encryption_warning": "警告",
"topline_expiring_certificate": "您的安全通訊端層SSL憑證即將到期。更新<0>加密設定</0>。", "topline_expiring_certificate": "您的安全通訊端層SSL憑證即將到期。更新<0>加密設定</0>。",
"topline_expired_certificate": "您的安全通訊端層SSL憑證為已到期的。更新<0>加密設定</0>。", "topline_expired_certificate": "您的安全通訊端層SSL憑證為已到期的。更新<0>加密設定</0>。",
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼", "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
@@ -638,8 +637,5 @@
"safe_browsing": "安全瀏覽", "safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取提供)</i>", "served_from_cache": "{{value}} <i>(由快取提供)</i>",
"form_error_password_length": "密碼必須為至少長 {{value}} 個字元", "form_error_password_length": "密碼必須為至少長 {{value}} 個字元",
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。", "anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。"
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
"cache_cleared": "DNS 快取被成功地清除",
"clear_cache": "清除快取"
} }

View File

@@ -1,5 +1,4 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import i18next from 'i18next';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { splitByNewLine } from '../helpers/helpers'; import { splitByNewLine } from '../helpers/helpers';
@@ -20,22 +19,6 @@ export const getDnsConfig = () => async (dispatch) => {
} }
}; };
export const clearDnsCacheRequest = createAction('CLEAR_DNS_CACHE_REQUEST');
export const clearDnsCacheFailure = createAction('CLEAR_DNS_CACHE_FAILURE');
export const clearDnsCacheSuccess = createAction('CLEAR_DNS_CACHE_SUCCESS');
export const clearDnsCache = () => async (dispatch) => {
dispatch(clearDnsCacheRequest());
try {
const data = await apiClient.clearCache();
dispatch(clearDnsCacheSuccess(data));
dispatch(addSuccessToast(i18next.t('cache_cleared')));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(clearDnsCacheFailure());
}
};
export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST'); export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST');
export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE'); export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE');
export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS'); export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');

View File

@@ -593,14 +593,6 @@ class Api {
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
// Cache
CLEAR_CACHE = { path: 'cache_clear', method: 'POST' };
clearCache() {
const { path, method } = this.CLEAR_CACHE;
return this.makeRequest(path, method);
}
} }
const apiClient = new Api(); const apiClient = new Api();

View File

@@ -74,6 +74,7 @@ const FormDHCPv4 = ({
className="form-control" className="form-control"
placeholder={t(ipv4placeholders.subnet_mask)} placeholder={t(ipv4placeholders.subnet_mask)}
validate={[ validate={[
validateIpv4,
validateRequired, validateRequired,
validateGatewaySubnetMask, validateGatewaySubnetMask,
]} ]}
@@ -96,6 +97,7 @@ const FormDHCPv4 = ({
placeholder={t(ipv4placeholders.range_start)} placeholder={t(ipv4placeholders.range_start)}
validate={[ validate={[
validateIpv4, validateIpv4,
validateGatewaySubnetMask,
validateIpForGatewaySubnetMask, validateIpForGatewaySubnetMask,
]} ]}
disabled={!isInterfaceIncludesIpv4} disabled={!isInterfaceIncludesIpv4}
@@ -111,6 +113,7 @@ const FormDHCPv4 = ({
validate={[ validate={[
validateIpv4, validateIpv4,
validateIpv4RangeEnd, validateIpv4RangeEnd,
validateGatewaySubnetMask,
validateIpForGatewaySubnetMask, validateIpForGatewaySubnetMask,
]} ]}
disabled={!isInterfaceIncludesIpv4} disabled={!isInterfaceIncludesIpv4}

View File

@@ -2,12 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { renderInputField, toNumber, CheckboxField } from '../../../../helpers/form'; import { renderInputField, toNumber, CheckboxField } from '../../../../helpers/form';
import { CACHE_CONFIG_FIELDS, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants'; import { CACHE_CONFIG_FIELDS, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
import { replaceZeroWithEmptyString } from '../../../../helpers/helpers'; import { replaceZeroWithEmptyString } from '../../../../helpers/helpers';
import { clearDnsCache } from '../../../../actions/dnsConfig';
const INPUTS_FIELDS = [ const INPUTS_FIELDS = [
{ {
@@ -34,7 +32,6 @@ const Form = ({
handleSubmit, submitting, invalid, handleSubmit, submitting, invalid,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch();
const { processingSetConfig } = useSelector((state) => state.dnsConfig, shallowEqual); const { processingSetConfig } = useSelector((state) => state.dnsConfig, shallowEqual);
const { const {
@@ -43,12 +40,6 @@ const Form = ({
const minExceedsMax = cache_ttl_min > cache_ttl_max; const minExceedsMax = cache_ttl_min > cache_ttl_max;
const handleClearCache = () => {
if (window.confirm(t('confirm_dns_cache_clear'))) {
dispatch(clearDnsCache());
}
};
return <form onSubmit={handleSubmit}> return <form onSubmit={handleSubmit}>
<div className="row"> <div className="row">
{INPUTS_FIELDS.map(({ {INPUTS_FIELDS.map(({
@@ -106,13 +97,6 @@ const Form = ({
> >
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>
<button
type="button"
className="btn btn-outline-secondary btn-standard form__button"
onClick={handleClearCache}
>
<Trans>clear_cache</Trans>
</button>
</form>; </form>;
}; };

View File

@@ -26,199 +26,199 @@ export default {
"name": "1Hosts (Lite)", "name": "1Hosts (Lite)",
"categoryId": "general", "categoryId": "general",
"homepage": "https://badmojr.github.io/1Hosts/", "homepage": "https://badmojr.github.io/1Hosts/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_24.txt" "source": "https://badmojr.gitlab.io/1hosts/Lite/adblock.txt"
}, },
"CHN_adrules": { "CHN_adrules": {
"name": "CHN: AdRules DNS List", "name": "CHN: AdRules DNS List",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/Cats-Team/AdRules", "homepage": "https://github.com/Cats-Team/AdRules",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_29.txt" "source": "https://raw.githubusercontent.com/Cats-Team/AdRules/main/dns.txt"
}, },
"CHN_anti_ad": { "CHN_anti_ad": {
"name": "CHN: anti-AD", "name": "CHN: anti-AD",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://anti-ad.net/", "homepage": "https://anti-ad.net/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_21.txt" "source": "https://anti-ad.net/easylist.txt"
}, },
"IDN_abpindo": { "IDN_abpindo": {
"name": "IDN: ABPindo", "name": "IDN: ABPindo",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/ABPindo/indonesianadblockrules", "homepage": "https://github.com/ABPindo/indonesianadblockrules",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_22.txt" "source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/aghome.txt"
}, },
"IRN_unwanted_iranian_domains": { "IRN_unwanted_iranian_domains": {
"name": "IRN: PersianBlocker list", "name": "IRN: PersianBlocker list",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/MasterKia/PersianBlocker", "homepage": "https://github.com/MasterKia/PersianBlocker",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.txt" "source": "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlockerHosts.txt"
}, },
"ITA_filtri_dns": { "ITA_filtri_dns": {
"name": "ITA: Filtri-DNS", "name": "ITA: Filtri-DNS",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://filtri-dns.ga/", "homepage": "https://filtri-dns.ga/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt" "source": "https://filtri-dns.ga/filtri.txt"
}, },
"KOR_list_kr": { "KOR_list_kr": {
"name": "KOR: List-KR DNS", "name": "KOR: List-KR DNS",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/List-KR/List-KR", "homepage": "https://github.com/List-KR/List-KR",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_25.txt" "source": "https://github.com/List-KR/List-KR"
}, },
"KOR_youslist": { "KOR_youslist": {
"name": "KOR: YousList", "name": "KOR: YousList",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/yous/YousList", "homepage": "https://github.com/yous/YousList",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_15.txt" "source": "https://raw.githubusercontent.com/yous/YousList/master/hosts.txt"
}, },
"MKD_macedonian_pi_hole_blocklist": { "MKD_macedonian_pi_hole_blocklist": {
"name": "MKD: Macedonian Pi-hole Blocklist", "name": "MKD: Macedonian Pi-hole Blocklist",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/cchevy/macedonian-pi-hole-blocklist", "homepage": "https://github.com/cchevy/macedonian-pi-hole-blocklist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_20.txt" "source": "https://raw.githubusercontent.com/cchevy/macedonian-pi-hole-blocklist/master/hosts.txt"
}, },
"NOR_dandelion_sprouts_anti_malware_list": { "NOR_dandelion_sprouts_anti_malware_list": {
"name": "NOR: Dandelion Sprouts nordiske filtre", "name": "NOR: Dandelion Sprouts nordiske filtre",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_13.txt" "source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/NordicFiltersAdGuardHome.txt"
}, },
"POL_polish_filters_for_pi_hole": { "POL_polish_filters_for_pi_hole": {
"name": "POL: Polish filters for Pi hole", "name": "POL: Polish filters for Pi hole",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://www.certyficate.it/", "homepage": "https://www.certyficate.it/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_14.txt" "source": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/hostfile.txt"
}, },
"SWE_frellwit_swedish_hosts_file": { "SWE_frellwit_swedish_hosts_file": {
"name": "SWE: Frellwit's Swedish Hosts File", "name": "SWE: Frellwit's Swedish Hosts File",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/lassekongo83/Frellwits-filter-lists/", "homepage": "https://github.com/lassekongo83/Frellwits-filter-lists/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_17.txt" "source": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Hosts-File.txt"
}, },
"TUR_turk_adlist": { "TUR_turk_adlist": {
"name": "TUR: turk-adlist", "name": "TUR: turk-adlist",
"categoryId": "regional", "categoryId": "regional",
"homepage": "https://github.com/bkrucarci/turk-adlist", "homepage": "https://github.com/bkrucarci/turk-adlist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_26.txt" "source": "https://raw.githubusercontent.com/bkrucarci/turk-adlist/master/hosts"
}, },
"VNM_abpvn": { "VNM_abpvn": {
"name": "VNM: ABPVN List", "name": "VNM: ABPVN List",
"categoryId": "regional", "categoryId": "regional",
"homepage": "http://abpvn.com/", "homepage": "http://abpvn.com/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_16.txt" "source": "https://abpvn.com/android/abpvn.txt"
}, },
"adguard_dns_filter": { "adguard_dns_filter": {
"name": "AdGuard DNS filter", "name": "AdGuard DNS filter",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter", "homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt" "source": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
}, },
"adway_default_blocklist": { "adway_default_blocklist": {
"name": "AdAway Default Blocklist", "name": "AdAway Default Blocklist",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/AdAway/adaway.github.io/", "homepage": "https://github.com/AdAway/adaway.github.io/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt" "source": "https://adaway.org/hosts.txt"
}, },
"curben_phishing_filter": { "curben_phishing_filter": {
"name": "Phishing URL Blocklist (PhishTank and OpenPhish)", "name": "Phishing URL Blocklist (PhishTank and OpenPhish)",
"categoryId": "security", "categoryId": "security",
"homepage": "https://gitlab.com/malware-filter/phishing-filter", "homepage": "https://gitlab.com/malware-filter/phishing-filter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_30.txt" "source": "https://malware-filter.gitlab.io/malware-filter/phishing-filter-agh.txt"
}, },
"dan_pollocks_list": { "dan_pollocks_list": {
"name": "Dan Pollock's List", "name": "Dan Pollock's List",
"categoryId": "general", "categoryId": "general",
"homepage": "https://someonewhocares.org/", "homepage": "https://someonewhocares.org/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_4.txt" "source": "https://someonewhocares.org/hosts/zero/hosts"
}, },
"dandelion_sprouts_anti_malware_list": { "dandelion_sprouts_anti_malware_list": {
"name": "Dandelion Sprout's Anti-Malware List", "name": "Dandelion Sprout's Anti-Malware List",
"categoryId": "security", "categoryId": "security",
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt" "source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareAdGuardHome.txt"
}, },
"dandelion_sprouts_game_console_adblock_list": { "dandelion_sprouts_game_console_adblock_list": {
"name": "Dandelion Sprout's Game Console Adblock List", "name": "Dandelion Sprout's Game Console Adblock List",
"categoryId": "other", "categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt" "source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/GameConsoleAdblockList.txt"
}, },
"energized_spark": { "energized_spark": {
"name": "Energized Spark", "name": "Energized Spark",
"categoryId": "general", "categoryId": "general",
"homepage": "https://energized.pro/", "homepage": "https://energized.pro/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_28.txt" "source": "https://block.energized.pro/spark/formats/filter"
}, },
"nocoin_filter_list": { "nocoin_filter_list": {
"name": "NoCoin Filter List", "name": "NoCoin Filter List",
"categoryId": "security", "categoryId": "security",
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/", "homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_8.txt" "source": "https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/hosts.txt"
}, },
"notracking_hosts_blocklists": { "notracking_hosts_blocklists": {
"name": "The NoTracking blocklist", "name": "The NoTracking blocklist",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/notracking/hosts-blocklists", "homepage": "https://github.com/notracking/hosts-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_32.txt" "source": "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/adblock/adblock.txt"
}, },
"oisd_basic": { "oisd_basic": {
"name": "OISD Blocklist Basic", "name": "OISD Blocklist Basic",
"categoryId": "general", "categoryId": "general",
"homepage": "https://oisd.nl/", "homepage": "https://oisd.nl/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_5.txt" "source": "https://abp.oisd.nl/basic/"
}, },
"oisd_full": { "oisd_full": {
"name": "OISD Blocklist Full", "name": "OISD Blocklist Full",
"categoryId": "general", "categoryId": "general",
"homepage": "https://oisd.nl/", "homepage": "https://oisd.nl/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt" "source": "https://abp.oisd.nl/"
}, },
"perflyst_dandelion_sprout_smart_tv_blocklist_for_adguard_home": { "perflyst_dandelion_sprout_smart_tv_blocklist_for_adguard_home": {
"name": "Perflyst and Dandelion Sprout's Smart-TV Blocklist", "name": "Perflyst and Dandelion Sprout's Smart-TV Blocklist",
"categoryId": "other", "categoryId": "other",
"homepage": "https://github.com/Perflyst/PiHoleBlocklist", "homepage": "https://github.com/Perflyst/PiHoleBlocklist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_7.txt" "source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt"
}, },
"peter_lowe_list": { "peter_lowe_list": {
"name": "Peter Lowe's Blocklist", "name": "Peter Lowe's Blocklist",
"categoryId": "general", "categoryId": "general",
"homepage": "https://pgl.yoyo.org/adservers/", "homepage": "https://pgl.yoyo.org/adservers/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_3.txt" "source": "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus\u0026showintro=1\u0026mimetype=plaintext"
}, },
"scam_blocklist_by_durablenapkin": { "scam_blocklist_by_durablenapkin": {
"name": "Scam Blocklist by DurableNapkin", "name": "Scam Blocklist by DurableNapkin",
"categoryId": "security", "categoryId": "security",
"homepage": "https://github.com/durablenapkin/scamblocklist", "homepage": "https://github.com/durablenapkin/scamblocklist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_10.txt" "source": "https://raw.githubusercontent.com/durablenapkin/scamblocklist/master/adguard.txt"
}, },
"staklerware_indicators_list": { "staklerware_indicators_list": {
"name": "Stalkerware Indicators List", "name": "Stalkerware Indicators List",
"categoryId": "security", "categoryId": "security",
"homepage": "https://github.com/AssoEchap/stalkerware-indicators", "homepage": "https://github.com/AssoEchap/stalkerware-indicators",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_31.txt" "source": "https://raw.githubusercontent.com/AssoEchap/stalkerware-indicators/master/generated/hosts"
}, },
"steven_blacks_list": { "steven_blacks_list": {
"name": "Steven Black's List", "name": "Steven Black's List",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/StevenBlack/hosts", "homepage": "https://github.com/StevenBlack/hosts",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_33.txt" "source": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
}, },
"the_big_list_of_hacked_malware_web_sites": { "the_big_list_of_hacked_malware_web_sites": {
"name": "The Big List of Hacked Malware Web Sites", "name": "The Big List of Hacked Malware Web Sites",
"categoryId": "security", "categoryId": "security",
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites", "homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt" "source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hosts"
}, },
"urlhaus_filter_online": { "urlhaus_filter_online": {
"name": "Malicious URL Blocklist (URLHaus)", "name": "Malicious URL Blocklist (URLHaus)",
"categoryId": "security", "categoryId": "security",
"homepage": "https://gitlab.com/malware-filter/urlhaus-filter", "homepage": "https://gitlab.com/malware-filter/urlhaus-filter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt" "source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh.txt"
}, },
"windowsspyblocker_hosts_spy_rules": { "windowsspyblocker_hosts_spy_rules": {
"name": "WindowsSpyBlocker - Hosts spy rules", "name": "WindowsSpyBlocker - Hosts spy rules",
"categoryId": "other", "categoryId": "other",
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker", "homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt" "source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt"
} }
} }
} }

View File

@@ -77,11 +77,11 @@ export const validateNotInRange = (value, allValues) => {
const { range_start, range_end } = allValues.v4; const { range_start, range_end } = allValues.v4;
if (range_start && validateIpv4(range_start)) { if (range_start && validateIpv4(range_start)) {
return undefined; return 'form_error_ip4_range_start_format';
} }
if (range_end && validateIpv4(range_end)) { if (range_end && validateIpv4(range_end)) {
return undefined; return 'form_error_ip4_range_end_format';
} }
const isAboveMin = range_start && ip4ToInt(value) >= ip4ToInt(range_start); const isAboveMin = range_start && ip4ToInt(value) >= ip4ToInt(range_start);
@@ -94,6 +94,14 @@ export const validateNotInRange = (value, allValues) => {
}); });
} }
if (!range_end && isAboveMin) {
return 'lower_range_start_error';
}
if (!range_start && isBelowMax) {
return 'greater_range_end_error';
}
return undefined; return undefined;
}; };
@@ -110,7 +118,7 @@ export const validateGatewaySubnetMask = (_, allValues) => {
const { subnet_mask, gateway_ip } = allValues.v4; const { subnet_mask, gateway_ip } = allValues.v4;
if (validateIpv4(gateway_ip)) { if (validateIpv4(gateway_ip)) {
return 'gateway_or_subnet_invalid'; return 'form_error_ip4_gateway_format';
} }
return parseSubnetMask(subnet_mask) ? undefined : 'gateway_or_subnet_invalid'; return parseSubnetMask(subnet_mask) ? undefined : 'gateway_or_subnet_invalid';
@@ -130,10 +138,6 @@ export const validateIpForGatewaySubnetMask = (value, allValues) => {
gateway_ip, subnet_mask, gateway_ip, subnet_mask,
} = allValues.v4; } = allValues.v4;
if ((gateway_ip && validateIpv4(gateway_ip)) || (subnet_mask && validateIpv4(subnet_mask))) {
return undefined;
}
const subnetPrefix = parseSubnetMask(subnet_mask); const subnetPrefix = parseSubnetMask(subnet_mask);
if (!isIpInCidr(value, `${gateway_ip}/${subnetPrefix}`)) { if (!isIpInCidr(value, `${gateway_ip}/${subnetPrefix}`)) {

9
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.18 go 1.18
require ( require (
github.com/AdguardTeam/dnsproxy v0.46.4 github.com/AdguardTeam/dnsproxy v0.46.2
github.com/AdguardTeam/golibs v0.11.3 github.com/AdguardTeam/golibs v0.11.3
github.com/AdguardTeam/urlfilter v0.16.0 github.com/AdguardTeam/urlfilter v0.16.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
@@ -18,7 +18,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c
github.com/kardianos/service v1.2.2 github.com/kardianos/service v1.2.2
github.com/lucas-clemente/quic-go v0.31.0 github.com/lucas-clemente/quic-go v0.29.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
github.com/mdlayher/netlink v1.6.2 github.com/mdlayher/netlink v1.6.2
// TODO(a.garipov): This package is deprecated; find a new one or use // TODO(a.garipov): This package is deprecated; find a new one or use
@@ -47,14 +47,16 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v1.0.0 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mdlayher/packet v1.0.0 // indirect github.com/mdlayher/packet v1.0.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect github.com/mdlayher/socket v0.2.3 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.5.0 // indirect github.com/onsi/ginkgo/v2 v2.5.0 // indirect
github.com/onsi/gomega v1.24.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -63,4 +65,5 @@ require (
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.2.0 // indirect golang.org/x/tools v0.2.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
) )

60
go.sum
View File

@@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.46.4 h1:/+wnTG0q2TkGQyA1PeSsjv4/f5ZprGduKPSoOcG+rOU= github.com/AdguardTeam/dnsproxy v0.46.2 h1:ZUKM713Ts5meYQqk6cJkUBMCFSWqFPXTgjXkN4RI1Vo=
github.com/AdguardTeam/dnsproxy v0.46.4/go.mod h1:yYDMAH6ay2PxLcLtfVM3FUiyv/U9B/zYO+cIIssuJNU= github.com/AdguardTeam/dnsproxy v0.46.2/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.11.3 h1:Oif+REq2WLycQ2Xm3ZPmJdfftptss0HbGWbxdFaC310= github.com/AdguardTeam/golibs v0.11.3 h1:Oif+REq2WLycQ2Xm3ZPmJdfftptss0HbGWbxdFaC310=
@@ -25,9 +25,6 @@ github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG+
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -37,6 +34,8 @@ github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkq
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ= github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
@@ -48,6 +47,13 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -60,17 +66,15 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ= github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
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=
@@ -87,8 +91,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M= github.com/lucas-clemente/quic-go v0.29.2 h1:O8Mt0O6LpvEW+wfC40vZdcw0DngwYzoxq5xULZNzSI8=
github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucas-clemente/quic-go v0.29.2/go.mod h1:g6/h9YMmLuU54tL1gW25uIi3VlBp3uv+sBihplIuskE=
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g= github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
@@ -120,9 +124,19 @@ github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -154,6 +168,7 @@ github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcy
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME= github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
@@ -166,9 +181,11 @@ golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.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 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -178,8 +195,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
@@ -189,11 +208,14 @@ golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -202,10 +224,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -213,6 +238,7 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -243,6 +269,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
@@ -251,15 +278,26 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -15,7 +15,7 @@ import (
func defaultHostsPaths() (paths []string) { func defaultHostsPaths() (paths []string) {
sysDir, err := windows.GetSystemDirectory() sysDir, err := windows.GetSystemDirectory()
if err != nil { if err != nil {
log.Error("aghnet: getting system directory: %s", err) log.Error("getting system directory: %s", err)
return []string{} return []string{}
} }

View File

@@ -168,11 +168,11 @@ func IsOpenWrt() (ok bool) {
return isOpenWrt() return isOpenWrt()
} }
// RootDirFS returns the [fs.FS] rooted at the operating system's root. On // RootDirFS returns the fs.FS rooted at the operating system's root.
// Windows it returns the fs.FS rooted at the volume of the system directory
// (usually, C:).
func RootDirFS() (fsys fs.FS) { func RootDirFS() (fsys fs.FS) {
return rootDirFS() // Use empty string since os.DirFS implicitly prepends a slash to it. This
// behavior is undocumented but it currently works.
return os.DirFS("")
} }
// NotifyReconfigureSignal notifies c on receiving reconfigure signals. // NotifyReconfigureSignal notifies c on receiving reconfigure signals.

View File

@@ -3,17 +3,12 @@
package aghos package aghos
import ( import (
"io/fs"
"os" "os"
"os/signal" "os/signal"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func rootDirFS() (fsys fs.FS) {
return os.DirFS("/")
}
func notifyReconfigureSignal(c chan<- os.Signal) { func notifyReconfigureSignal(c chan<- os.Signal) {
signal.Notify(c, unix.SIGHUP) signal.Notify(c, unix.SIGHUP)
} }

View File

@@ -3,29 +3,13 @@
package aghos package aghos
import ( import (
"io/fs"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"syscall" "syscall"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func rootDirFS() (fsys fs.FS) {
// TODO(a.garipov): Use a better way if golang/go#44279 is ever resolved.
sysDir, err := windows.GetSystemDirectory()
if err != nil {
log.Error("aghos: getting root filesystem: %s; using C:", err)
// Assume that C: is the safe default.
return os.DirFS("C:")
}
return os.DirFS(filepath.VolumeName(sysDir))
}
func setRlimit(val uint64) (err error) { func setRlimit(val uint64) (err error) {
return Unsupported("setrlimit") return Unsupported("setrlimit")
} }

View File

@@ -137,14 +137,14 @@ func (c *V4ServerConf) Validate() (err error) {
gatewayIP, err := ensureV4(c.GatewayIP, "address") gatewayIP, err := ensureV4(c.GatewayIP, "address")
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is and there is // Don't wrap an errors since it's informative enough as is and there is
// an annotation deferred already. // an annotation deferred already.
return err return err
} }
subnetMask, err := ensureV4(c.SubnetMask, "subnet mask") subnetMask, err := ensureV4(c.SubnetMask, "subnet mask")
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is and there is // Don't wrap an errors since it's informative enough as is and there is
// an annotation deferred already. // an annotation deferred already.
return err return err
} }
@@ -155,21 +155,20 @@ func (c *V4ServerConf) Validate() (err error) {
rangeStart, err := ensureV4(c.RangeStart, "address") rangeStart, err := ensureV4(c.RangeStart, "address")
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is and there is // Don't wrap an errors since it's informative enough as is and there is
// an annotation deferred already. // an annotation deferred already.
return err return err
} }
rangeEnd, err := ensureV4(c.RangeEnd, "address") rangeEnd, err := ensureV4(c.RangeEnd, "address")
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is and there is // Don't wrap an errors since it's informative enough as is and there is
// an annotation deferred already. // an annotation deferred already.
return err return err
} }
c.ipRange, err = newIPRange(rangeStart.AsSlice(), rangeEnd.AsSlice()) c.ipRange, err = newIPRange(rangeStart.AsSlice(), rangeEnd.AsSlice())
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is and there is // Don't wrap an errors since it's informative enough as is and there is
// an annotation deferred already. // an annotation deferred already.
return err return err
} }

View File

@@ -219,6 +219,8 @@ var _ Interface = (*server)(nil)
// Create initializes and returns the DHCP server handling both address // Create initializes and returns the DHCP server handling both address
// families. It also registers the corresponding HTTP API endpoints. // families. It also registers the corresponding HTTP API endpoints.
//
// TODO(e.burkov): Don't register handlers, see TODO on [aghhttp.RegisterFunc].
func Create(conf *ServerConfig) (s *server, err error) { func Create(conf *ServerConfig) (s *server, err error) {
s = &server{ s = &server{
conf: &ServerConfig{ conf: &ServerConfig{
@@ -235,8 +237,6 @@ func Create(conf *ServerConfig) (s *server, err error) {
}, },
} }
// TODO(e.burkov): Don't register handlers, see TODO on
// [aghhttp.RegisterFunc].
s.registerHandlers() s.registerHandlers()
v4conf := conf.Conf4 v4conf := conf.Conf4
@@ -250,7 +250,7 @@ func Create(conf *ServerConfig) (s *server, err error) {
return nil, fmt.Errorf("creating dhcpv4 srv: %w", err) return nil, fmt.Errorf("creating dhcpv4 srv: %w", err)
} }
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err) log.Error("creating dhcpv4 srv: %s", err)
} }
v6conf := conf.Conf6 v6conf := conf.Conf6

View File

@@ -145,7 +145,8 @@ type FilteringConfig struct {
IpsetListFileName string `yaml:"ipset_file"` IpsetListFileName string `yaml:"ipset_file"`
} }
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, DNS-over-TLS,
// and DNS-over-QUIC.
type TLSConfig struct { type TLSConfig struct {
cert tls.Certificate cert tls.Certificate

View File

@@ -3,7 +3,6 @@ package dnsforward
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@@ -566,11 +565,6 @@ type domainSpecificTestError struct {
error error
} }
// Error implements the [error] interface for domainSpecificTestError.
func (err domainSpecificTestError) Error() (msg string) {
return fmt.Sprintf("WARNING: %s", err.error)
}
// checkDNS checks the upstream server defined by upstreamConfigStr using // checkDNS checks the upstream server defined by upstreamConfigStr using
// healthCheck for actually exchange messages. It uses bootstrap to resolve the // healthCheck for actually exchange messages. It uses bootstrap to resolve the
// upstream's address. // upstream's address.
@@ -637,54 +631,44 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
result := map[string]string{} result := map[string]string{}
bootstraps := req.BootstrapDNS bootstraps := req.BootstrapDNS
timeout := s.conf.UpstreamTimeout timeout := s.conf.UpstreamTimeout
for _, host := range req.Upstreams {
err = checkDNS(host, bootstraps, timeout, checkDNSUpstreamExc)
if err != nil {
log.Info("%v", err)
result[host] = err.Error()
if _, ok := err.(domainSpecificTestError); ok {
result[host] = fmt.Sprintf("WARNING: %s", result[host])
}
type upsCheckResult = struct { continue
res string
host string
}
upsNum := len(req.Upstreams) + len(req.PrivateUpstreams)
resCh := make(chan upsCheckResult, upsNum)
checkUps := func(ups string, healthCheck healthCheckFunc) {
res := upsCheckResult{
host: ups,
} }
defer func() { resCh <- res }()
checkErr := checkDNS(ups, bootstraps, timeout, healthCheck) result[host] = "OK"
if checkErr != nil { }
res.res = checkErr.Error()
} else { for _, host := range req.PrivateUpstreams {
res.res = "OK" err = checkDNS(host, bootstraps, timeout, checkPrivateUpstreamExc)
if err != nil {
log.Info("%v", err)
// TODO(e.burkov): If passed upstream have already written an error
// above, we rewriting the error for it. These cases should be
// handled properly instead.
result[host] = err.Error()
if _, ok := err.(domainSpecificTestError); ok {
result[host] = fmt.Sprintf("WARNING: %s", result[host])
}
continue
} }
}
for _, ups := range req.Upstreams { result[host] = "OK"
go checkUps(ups, checkDNSUpstreamExc)
} }
for _, ups := range req.PrivateUpstreams {
go checkUps(ups, checkPrivateUpstreamExc)
}
for i := 0; i < upsNum; i++ {
pair := <-resCh
// TODO(e.burkov): The upstreams used for both common and private
// resolving should be reported separately.
result[pair.host] = pair.res
}
close(resCh)
_ = aghhttp.WriteJSONResponse(w, r, result) _ = aghhttp.WriteJSONResponse(w, r, result)
} }
// handleCacheClear is the handler for the POST /control/cache_clear HTTP API.
func (s *Server) handleCacheClear(w http.ResponseWriter, _ *http.Request) {
s.dnsProxy.ClearCache()
_, _ = io.WriteString(w, "OK")
}
// handleDoH is the DNS-over-HTTPs handler. // handleDoH is the DNS-over-HTTPs handler.
// //
// Control flow: // Control flow:
@@ -719,8 +703,6 @@ func (s *Server) registerHandlers() {
s.conf.HTTPRegister(http.MethodGet, "/control/access/list", s.handleAccessList) s.conf.HTTPRegister(http.MethodGet, "/control/access/list", s.handleAccessList)
s.conf.HTTPRegister(http.MethodPost, "/control/access/set", s.handleAccessSet) s.conf.HTTPRegister(http.MethodPost, "/control/access/set", s.handleAccessSet)
s.conf.HTTPRegister(http.MethodPost, "/control/cache_clear", s.handleCacheClear)
// Register both versions, with and without the trailing slash, to // Register both versions, with and without the trailing slash, to
// prevent a 301 Moved Permanently redirect when clients request the // prevent a 301 Moved Permanently redirect when clients request the
// path without the trailing slash. Those redirects break some clients. // path without the trailing slash. Those redirects break some clients.

View File

@@ -7,20 +7,16 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/netip"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -396,141 +392,3 @@ func TestValidateUpstreamsPrivate(t *testing.T) {
}) })
} }
} }
func newLocalUpstreamListener(t *testing.T, port int, handler dns.Handler) (real net.Addr) {
startCh := make(chan struct{})
upsSrv := &dns.Server{
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(port)).String(),
Net: "tcp",
Handler: handler,
NotifyStartedFunc: func() { close(startCh) },
}
go func() {
t := testutil.PanicT{}
err := upsSrv.ListenAndServe()
require.NoError(t, err)
}()
<-startCh
testutil.CleanupAndRequireSuccess(t, upsSrv.Shutdown)
return upsSrv.Listener.Addr()
}
func TestServer_handleTestUpstreaDNS(t *testing.T) {
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, err)
})
badHandler := dns.HandlerFunc(func(w dns.ResponseWriter, _ *dns.Msg) {
err := w.WriteMsg(new(dns.Msg))
require.NoError(testutil.PanicT{}, err)
})
goodUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, goodHandler).String(),
}).String()
badUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
}).String()
const upsTimeout = 100 * time.Millisecond
srv := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
UpstreamTimeout: upsTimeout,
}, nil)
startDeferStop(t, srv)
testCases := []struct {
body map[string]any
wantResp map[string]any
name string
}{{
body: map[string]any{
"upstream_dns": []string{goodUps},
},
wantResp: map[string]any{
goodUps: "OK",
},
name: "success",
}, {
body: map[string]any{
"upstream_dns": []string{badUps},
},
wantResp: map[string]any{
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: dns: id mismatch`,
},
name: "broken",
}, {
body: map[string]any{
"upstream_dns": []string{goodUps, badUps},
},
wantResp: map[string]any{
goodUps: "OK",
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: dns: id mismatch`,
},
name: "both",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqBody, err := json.Marshal(tc.body)
require.NoError(t, err)
w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r)
require.Equal(t, http.StatusOK, w.Code)
resp := map[string]any{}
err = json.NewDecoder(w.Body).Decode(&resp)
require.NoError(t, err)
assert.Equal(t, tc.wantResp, resp)
})
}
t.Run("timeout", func(t *testing.T) {
slowHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
time.Sleep(upsTimeout * 2)
writeErr := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, writeErr)
})
sleepyUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, slowHandler).String(),
}).String()
req := map[string]any{
"upstream_dns": []string{sleepyUps},
}
reqBody, err := json.Marshal(req)
require.NoError(t, err)
w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r)
require.Equal(t, http.StatusOK, w.Code)
resp := map[string]any{}
err = json.NewDecoder(w.Body).Decode(&resp)
require.NoError(t, err)
require.Contains(t, resp, sleepyUps)
require.IsType(t, "", resp[sleepyUps])
sleepyRes, _ := resp[sleepyUps].(string)
// TODO(e.burkov): Improve the format of an error in dnsproxy.
assert.True(t, strings.HasSuffix(sleepyRes, "i/o timeout"))
})
}

View File

@@ -33,7 +33,6 @@ import (
// The IDs of built-in filter lists. // The IDs of built-in filter lists.
// //
// Keep in sync with client/src/helpers/constants.js. // Keep in sync with client/src/helpers/constants.js.
// TODO(d.kolyshev): Add RewritesListID and don't forget to keep in sync.
const ( const (
CustomListID = -iota CustomListID = -iota
SysHostsListID SysHostsListID

View File

@@ -1,73 +0,0 @@
package rewrite
import (
"fmt"
"net/netip"
"strings"
"github.com/miekg/dns"
)
// Item is a single DNS rewrite record.
type Item struct {
// Domain is the domain pattern for which this rewrite should work.
Domain string `yaml:"domain"`
// Answer is the IP address, canonical name, or one of the special
// values: "A" or "AAAA".
Answer string `yaml:"answer"`
}
// equal returns true if rw is equal to other.
func (rw *Item) equal(other *Item) (ok bool) {
if rw == nil {
return other == nil
} else if other == nil {
return false
}
return *rw == *other
}
// toRule converts rw to a filter rule.
func (rw *Item) toRule() (res string) {
if rw == nil {
return ""
}
domain := strings.ToLower(rw.Domain)
dType, exception := rw.rewriteParams()
dTypeKey := dns.TypeToString[dType]
if exception {
return fmt.Sprintf("@@||%s^$dnstype=%s,dnsrewrite", domain, dTypeKey)
}
return fmt.Sprintf("|%s^$dnsrewrite=NOERROR;%s;%s", domain, dTypeKey, rw.Answer)
}
// rewriteParams returns dns request type and exception flag for rw.
func (rw *Item) rewriteParams() (dType uint16, exception bool) {
switch rw.Answer {
case "AAAA":
return dns.TypeAAAA, true
case "A":
return dns.TypeA, true
default:
// Go on.
}
addr, err := netip.ParseAddr(rw.Answer)
if err != nil {
// TODO(d.kolyshev): Validate rw.Answer as a domain name.
return dns.TypeCNAME, false
}
if addr.Is4() {
dType = dns.TypeA
} else {
dType = dns.TypeAAAA
}
return dType, false
}

View File

@@ -1,124 +0,0 @@
package rewrite
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestItem_equal(t *testing.T) {
const (
testDomain = "example.org"
testAnswer = "1.1.1.1"
)
testItem := &Item{
Domain: testDomain,
Answer: testAnswer,
}
testCases := []struct {
name string
left *Item
right *Item
want bool
}{{
name: "nil_left",
left: nil,
right: testItem,
want: false,
}, {
name: "nil_right",
left: testItem,
right: nil,
want: false,
}, {
name: "nils",
left: nil,
right: nil,
want: true,
}, {
name: "equal",
left: testItem,
right: testItem,
want: true,
}, {
name: "distinct",
left: testItem,
right: &Item{
Domain: "other",
Answer: "other",
},
want: false,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res := tc.left.equal(tc.right)
assert.Equal(t, tc.want, res)
})
}
}
func TestItem_toRule(t *testing.T) {
const testDomain = "example.org"
testCases := []struct {
name string
item *Item
want string
}{{
name: "nil",
item: nil,
want: "",
}, {
name: "a_rule",
item: &Item{
Domain: testDomain,
Answer: "1.1.1.1",
},
want: "|example.org^$dnsrewrite=NOERROR;A;1.1.1.1",
}, {
name: "aaaa_rule",
item: &Item{
Domain: testDomain,
Answer: "1:2:3::4",
},
want: "|example.org^$dnsrewrite=NOERROR;AAAA;1:2:3::4",
}, {
name: "cname_rule",
item: &Item{
Domain: testDomain,
Answer: "other.org",
},
want: "|example.org^$dnsrewrite=NOERROR;CNAME;other.org",
}, {
name: "wildcard_rule",
item: &Item{
Domain: "*.example.org",
Answer: "other.org",
},
want: "|*.example.org^$dnsrewrite=NOERROR;CNAME;other.org",
}, {
name: "aaaa_exception",
item: &Item{
Domain: testDomain,
Answer: "A",
},
want: "@@||example.org^$dnstype=A,dnsrewrite",
}, {
name: "aaaa_exception",
item: &Item{
Domain: testDomain,
Answer: "AAAA",
},
want: "@@||example.org^$dnstype=AAAA,dnsrewrite",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res := tc.item.toRule()
assert.Equal(t, tc.want, res)
})
}
}

View File

@@ -1,241 +0,0 @@
// Package rewrite implements DNS Rewrites storage and request matching.
package rewrite
import (
"fmt"
"strings"
"sync"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// Storage is a storage for rewrite rules.
type Storage interface {
// MatchRequest returns matching dnsrewrites for the specified request.
MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite)
// Add adds item to the storage.
Add(item *Item) (err error)
// Remove deletes item from the storage.
Remove(item *Item) (err error)
// List returns all items from the storage.
List() (items []*Item)
}
// DefaultStorage is the default storage for rewrite rules.
type DefaultStorage struct {
// mu protects items.
mu *sync.RWMutex
// engine is the DNS filtering engine.
engine *urlfilter.DNSEngine
// ruleList is the filtering rule ruleList used by the engine.
ruleList filterlist.RuleList
// rewrites stores the rewrite entries from configuration.
rewrites []*Item
// urlFilterID is the synthetic integer identifier for the urlfilter engine.
//
// TODO(a.garipov): Change the type to a string in module urlfilter and
// remove this crutch.
urlFilterID int
}
// NewDefaultStorage returns new rewrites storage. listID is used as an
// identifier of the underlying rules list. rewrites must not be nil.
func NewDefaultStorage(listID int, rewrites []*Item) (s *DefaultStorage, err error) {
s = &DefaultStorage{
mu: &sync.RWMutex{},
urlFilterID: listID,
rewrites: rewrites,
}
s.mu.Lock()
defer s.mu.Unlock()
err = s.resetRules()
if err != nil {
return nil, err
}
return s, nil
}
// type check
var _ Storage = (*DefaultStorage)(nil)
// MatchRequest implements the [Storage] interface for *DefaultStorage.
func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite) {
s.mu.RLock()
defer s.mu.RUnlock()
rrules := s.rewriteRulesForReq(dReq)
if len(rrules) == 0 {
return nil
}
// TODO(a.garipov): Check cnames for cycles on initialisation.
cnames := stringutil.NewSet()
host := dReq.Hostname
for len(rrules) > 0 && rrules[0].DNSRewrite != nil && rrules[0].DNSRewrite.NewCNAME != "" {
rule := rrules[0]
rwAns := rule.DNSRewrite.NewCNAME
log.Debug("rewrite: cname for %s is %s", host, rwAns)
if dReq.Hostname == rwAns {
// A request for the hostname itself is an exception rule.
// TODO(d.kolyshev): Check rewrite of a pattern onto itself.
return nil
}
if host == rwAns && isWildcard(rule.RuleText) {
// An "*.example.com → sub.example.com" rewrite matching in a loop.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/4016.
return []*rules.DNSRewrite{rule.DNSRewrite}
}
if cnames.Has(rwAns) {
log.Info("rewrite: cname loop for %q on %q", dReq.Hostname, rwAns)
return nil
}
cnames.Add(rwAns)
drules := s.rewriteRulesForReq(&urlfilter.DNSRequest{
Hostname: rwAns,
DNSType: dReq.DNSType,
})
if drules != nil {
rrules = drules
}
host = rwAns
}
return s.collectDNSRewrites(rrules, dReq.DNSType)
}
// collectDNSRewrites filters DNSRewrite by question type.
func (s *DefaultStorage) collectDNSRewrites(
rewrites []*rules.NetworkRule,
qtyp uint16,
) (rws []*rules.DNSRewrite) {
for _, rewrite := range rewrites {
dnsRewrite := rewrite.DNSRewrite
if matchesQType(dnsRewrite, qtyp) {
rws = append(rws, dnsRewrite)
}
}
return rws
}
// rewriteRulesForReq returns matching dnsrewrite rules.
func (s *DefaultStorage) rewriteRulesForReq(dReq *urlfilter.DNSRequest) (rules []*rules.NetworkRule) {
res, _ := s.engine.MatchRequest(dReq)
return res.DNSRewrites()
}
// Add implements the [Storage] interface for *DefaultStorage.
func (s *DefaultStorage) Add(item *Item) (err error) {
s.mu.Lock()
defer s.mu.Unlock()
// TODO(d.kolyshev): Handle duplicate items.
s.rewrites = append(s.rewrites, item)
return s.resetRules()
}
// Remove implements the [Storage] interface for *DefaultStorage.
func (s *DefaultStorage) Remove(item *Item) (err error) {
s.mu.Lock()
defer s.mu.Unlock()
arr := []*Item{}
// TODO(d.kolyshev): Use slices.IndexFunc + slices.Delete?
for _, ent := range s.rewrites {
if ent.equal(item) {
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer)
continue
}
arr = append(arr, ent)
}
s.rewrites = arr
return s.resetRules()
}
// List implements the [Storage] interface for *DefaultStorage.
func (s *DefaultStorage) List() (items []*Item) {
s.mu.RLock()
defer s.mu.RUnlock()
return slices.Clone(s.rewrites)
}
// resetRules resets the filtering rules.
func (s *DefaultStorage) resetRules() (err error) {
// TODO(a.garipov): Use strings.Builder.
var rulesText []string
for _, rewrite := range s.rewrites {
rulesText = append(rulesText, rewrite.toRule())
}
strList := &filterlist.StringRuleList{
ID: s.urlFilterID,
RulesText: strings.Join(rulesText, "\n"),
IgnoreCosmetic: true,
}
rs, err := filterlist.NewRuleStorage([]filterlist.RuleList{strList})
if err != nil {
return fmt.Errorf("creating list storage: %w", err)
}
s.ruleList = strList
s.engine = urlfilter.NewDNSEngine(rs)
log.Info("rewrite: filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount)
return nil
}
// matchesQType returns true if dnsrewrite matches the question type qt.
func matchesQType(dnsrr *rules.DNSRewrite, qt uint16) (ok bool) {
// Add CNAMEs, since they match for all types requests.
if dnsrr.RRType == dns.TypeCNAME {
return true
}
// Reject types other than A and AAAA.
if qt != dns.TypeA && qt != dns.TypeAAAA {
return false
}
return dnsrr.RRType == qt
}
// isWildcard returns true if pat is a wildcard domain pattern.
func isWildcard(pat string) (res bool) {
return strings.HasPrefix(pat, "|*.")
}

View File

@@ -1,458 +0,0 @@
package rewrite
import (
"net"
"testing"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDefaultStorage(t *testing.T) {
items := []*Item{{
Domain: "example.com",
Answer: "answer.com",
}}
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
require.Len(t, s.List(), 1)
}
func TestDefaultStorage_CRUD(t *testing.T) {
var items []*Item
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
require.Len(t, s.List(), 0)
item := &Item{Domain: "example.com", Answer: "answer.com"}
err = s.Add(item)
require.NoError(t, err)
list := s.List()
require.Len(t, list, 1)
require.True(t, item.equal(list[0]))
err = s.Remove(item)
require.NoError(t, err)
require.Len(t, s.List(), 0)
}
func TestDefaultStorage_MatchRequest(t *testing.T) {
items := []*Item{{
// This one and below are about CNAME, A and AAAA.
Domain: "somecname",
Answer: "somehost.com",
}, {
Domain: "somehost.com",
Answer: "0.0.0.0",
}, {
Domain: "host.com",
Answer: "1.2.3.4",
}, {
Domain: "host.com",
Answer: "1.2.3.5",
}, {
Domain: "host.com",
Answer: "1:2:3::4",
}, {
Domain: "www.host.com",
Answer: "host.com",
}, {
// This one is a wildcard.
Domain: "*.host.com",
Answer: "1.2.3.5",
}, {
// This one and below are about wildcard overriding.
Domain: "a.host.com",
Answer: "1.2.3.4",
}, {
// This one is about CNAME and wildcard interacting.
Domain: "*.host2.com",
Answer: "host.com",
}, {
// This one and below are about 2 level CNAME.
Domain: "b.host.com",
Answer: "somecname",
}, {
// This one and below are about 2 level CNAME and wildcard.
Domain: "b.host3.com",
Answer: "a.host3.com",
}, {
Domain: "a.host3.com",
Answer: "x.host.com",
}, {
Domain: "*.hostboth.com",
Answer: "1.2.3.6",
}, {
Domain: "*.hostboth.com",
Answer: "1234::5678",
}, {
Domain: "BIGHOST.COM",
Answer: "1.2.3.7",
}, {
Domain: "*.issue4016.com",
Answer: "sub.issue4016.com",
}}
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
testCases := []struct {
name string
host string
wantDNSRewrites []*rules.DNSRewrite
dtyp uint16
}{{
name: "not_filtered_not_found",
host: "hoost.com",
wantDNSRewrites: nil,
dtyp: dns.TypeA,
}, {
name: "not_filtered_qtype",
host: "www.host.com",
wantDNSRewrites: nil,
dtyp: dns.TypeMX,
}, {
name: "rewritten_a",
host: "www.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 4}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}, {
Value: net.IP{1, 2, 3, 5}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "rewritten_aaaa",
host: "www.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.ParseIP("1:2:3::4"),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeAAAA,
}},
dtyp: dns.TypeAAAA,
}, {
name: "wildcard_match",
host: "abc.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 5}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
//}, {
// TODO(d.kolyshev): This is about matching in urlfilter.
// name: "wildcard_override",
// host: "a.host.com",
// wantDNSRewrites: []*rules.DNSRewrite{{
// Value: net.IP{1, 2, 3, 4}.To16(),
// NewCNAME: "",
// RCode: dns.RcodeSuccess,
// RRType: dns.TypeA,
// }},
// dtyp: dns.TypeA,
}, {
name: "wildcard_cname_interaction",
host: "www.host2.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 4}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}, {
Value: net.IP{1, 2, 3, 5}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "two_cnames",
host: "b.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{0, 0, 0, 0}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "two_cnames_and_wildcard",
host: "b.host3.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 5}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "issue3343",
host: "www.hostboth.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.ParseIP("1234::5678"),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeAAAA,
}},
dtyp: dns.TypeAAAA,
}, {
name: "issue3351",
host: "bighost.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 7}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "issue4008",
host: "somehost.com",
wantDNSRewrites: nil,
dtyp: dns.TypeHTTPS,
}, {
name: "issue4016",
host: "www.issue4016.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: nil,
NewCNAME: "sub.issue4016.com",
RCode: dns.RcodeSuccess,
RRType: dns.TypeNone,
}},
dtyp: dns.TypeA,
}, {
name: "issue4016_self",
host: "sub.issue4016.com",
wantDNSRewrites: nil,
dtyp: dns.TypeA,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
Hostname: tc.host,
DNSType: tc.dtyp,
})
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
})
}
}
func TestDefaultStorage_MatchRequest_Levels(t *testing.T) {
// Exact host, wildcard L2, wildcard L3.
items := []*Item{{
Domain: "host.com",
Answer: "1.1.1.1",
}, {
Domain: "*.host.com",
Answer: "2.2.2.2",
}, {
Domain: "*.sub.host.com",
Answer: "3.3.3.3",
}}
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
testCases := []struct {
name string
host string
wantDNSRewrites []*rules.DNSRewrite
dtyp uint16
}{{
name: "exact_match",
host: "host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 1, 1, 1}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "l2_match",
host: "sub.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{2, 2, 2, 2}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
//}, {
// TODO(d.kolyshev): This is about matching in urlfilter.
// name: "l3_match",
// host: "my.sub.host.com",
// wantDNSRewrites: []*rules.DNSRewrite{{
// Value: net.IP{3, 3, 3, 3}.To16(),
// NewCNAME: "",
// RCode: dns.RcodeSuccess,
// RRType: dns.TypeA,
// }},
// dtyp: dns.TypeA,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
Hostname: tc.host,
DNSType: tc.dtyp,
})
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
})
}
}
func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) {
// Wildcard and exception for a sub-domain.
items := []*Item{{
Domain: "*.host.com",
Answer: "2.2.2.2",
}, {
Domain: "sub.host.com",
Answer: "sub.host.com",
}, {
Domain: "*.sub.host.com",
Answer: "*.sub.host.com",
}}
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
testCases := []struct {
name string
host string
wantDNSRewrites []*rules.DNSRewrite
dtyp uint16
}{{
name: "match_subdomain",
host: "my.host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{2, 2, 2, 2}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "exception_cname",
host: "sub.host.com",
wantDNSRewrites: nil,
dtyp: dns.TypeA,
//}, {
// TODO(d.kolyshev): This is about matching in urlfilter.
// name: "exception_wildcard",
// host: "my.sub.host.com",
// wantDNSRewrites: nil,
// dtyp: dns.TypeA,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
Hostname: tc.host,
DNSType: tc.dtyp,
})
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
})
}
}
func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) {
// Exception for AAAA record.
items := []*Item{{
Domain: "host.com",
Answer: "1.2.3.4",
}, {
Domain: "host.com",
Answer: "AAAA",
}, {
Domain: "host2.com",
Answer: "::1",
}, {
Domain: "host2.com",
Answer: "A",
}, {
Domain: "host3.com",
Answer: "A",
}}
s, err := NewDefaultStorage(-1, items)
require.NoError(t, err)
testCases := []struct {
name string
host string
wantDNSRewrites []*rules.DNSRewrite
dtyp uint16
}{{
name: "match_A",
host: "host.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.IP{1, 2, 3, 4}.To16(),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeA,
}},
dtyp: dns.TypeA,
}, {
name: "exception_AAAA_host.com",
host: "host.com",
wantDNSRewrites: nil,
dtyp: dns.TypeAAAA,
}, {
name: "exception_A_host2.com",
host: "host2.com",
wantDNSRewrites: nil,
dtyp: dns.TypeA,
}, {
name: "match_AAAA_host2.com",
host: "host2.com",
wantDNSRewrites: []*rules.DNSRewrite{{
Value: net.ParseIP("::1"),
NewCNAME: "",
RCode: dns.RcodeSuccess,
RRType: dns.TypeAAAA,
}},
dtyp: dns.TypeAAAA,
}, {
name: "exception_A_host3.com",
host: "host3.com",
wantDNSRewrites: nil,
dtyp: dns.TypeA,
}, {
name: "match_AAAA_host3.com",
host: "host3.com",
wantDNSRewrites: nil,
dtyp: dns.TypeAAAA,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
Hostname: tc.host,
DNSType: tc.dtyp,
})
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
})
}
}

View File

@@ -1,93 +0,0 @@
package filtering
import (
"encoding/json"
"net/http"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
)
// TODO(d.kolyshev): Use [rewrite.Item] instead.
type rewriteEntryJSON struct {
Domain string `json:"domain"`
Answer string `json:"answer"`
}
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
arr := []*rewriteEntryJSON{}
d.confLock.Lock()
for _, ent := range d.Config.Rewrites {
jsent := rewriteEntryJSON{
Domain: ent.Domain,
Answer: ent.Answer,
}
arr = append(arr, &jsent)
}
d.confLock.Unlock()
_ = aghhttp.WriteJSONResponse(w, r, arr)
}
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
rwJSON := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&rwJSON)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
rw := &LegacyRewrite{
Domain: rwJSON.Domain,
Answer: rwJSON.Answer,
}
err = rw.normalize()
if err != nil {
// Shouldn't happen currently, since normalize only returns a non-nil
// error when a rewrite is nil, but be change-proof.
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
return
}
d.confLock.Lock()
d.Config.Rewrites = append(d.Config.Rewrites, rw)
d.confLock.Unlock()
log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites))
d.Config.ConfigModified()
}
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
jsent := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&jsent)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
entDel := &LegacyRewrite{
Domain: jsent.Domain,
Answer: jsent.Answer,
}
arr := []*LegacyRewrite{}
d.confLock.Lock()
for _, ent := range d.Config.Rewrites {
if ent.equal(entDel) {
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer)
continue
}
arr = append(arr, ent)
}
d.Config.Rewrites = arr
d.confLock.Unlock()
d.Config.ConfigModified()
}

View File

@@ -3,12 +3,16 @@
package filtering package filtering
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"net/http"
"sort" "sort"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@@ -217,3 +221,86 @@ func max(a, b int) int {
return b return b
} }
type rewriteEntryJSON struct {
Domain string `json:"domain"`
Answer string `json:"answer"`
}
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
arr := []*rewriteEntryJSON{}
d.confLock.Lock()
for _, ent := range d.Config.Rewrites {
jsent := rewriteEntryJSON{
Domain: ent.Domain,
Answer: ent.Answer,
}
arr = append(arr, &jsent)
}
d.confLock.Unlock()
_ = aghhttp.WriteJSONResponse(w, r, arr)
}
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
rwJSON := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&rwJSON)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
rw := &LegacyRewrite{
Domain: rwJSON.Domain,
Answer: rwJSON.Answer,
}
err = rw.normalize()
if err != nil {
// Shouldn't happen currently, since normalize only returns a non-nil
// error when a rewrite is nil, but be change-proof.
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
return
}
d.confLock.Lock()
d.Config.Rewrites = append(d.Config.Rewrites, rw)
d.confLock.Unlock()
log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites))
d.Config.ConfigModified()
}
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
jsent := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&jsent)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
entDel := &LegacyRewrite{
Domain: jsent.Domain,
Answer: jsent.Answer,
}
arr := []*LegacyRewrite{}
d.confLock.Lock()
for _, ent := range d.Config.Rewrites {
if ent.equal(entDel) {
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer)
continue
}
arr = append(arr, ent)
}
d.Config.Rewrites = arr
d.confLock.Unlock()
d.Config.ConfigModified()
}

View File

@@ -246,111 +246,6 @@ var blockedServices = []blockedService{{
Rules: []string{ Rules: []string{
"||mail.ru^", "||mail.ru^",
}, },
}, {
ID: "mastodon",
Name: "Mastodon",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 512 512\"><path d=\"M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z\"/></svg>"),
Rules: []string{
"||aus.social^",
"||awscommunity.social^",
"||dju.social^",
"||dresden.network^",
"||fedibird.com^",
"||fosstodon.org^",
"||glasgow.social^",
"||h4.io^",
"||hachyderm.io^",
"||hessen.social^",
"||hispagatos.space^",
"||home.social^",
"||hostux.social^",
"||ieji.de^",
"||indieweb.social^",
"||ioc.exchange^",
"||kolektiva.social^",
"||livellosegreto.it^",
"||lor.sh^",
"||m.cmx.im^",
"||mas.to^",
"||masto.ai^",
"||masto.es^",
"||masto.nobigtech.es^",
"||masto.pt^",
"||mastodon.au^",
"||mastodon.bida.im^",
"||mastodon.com.tr^",
"||mastodon.eus^",
"||mastodon.ie^",
"||mastodon.iriseden.eu^",
"||mastodon.lol^",
"||mastodon.nl^",
"||mastodon.nu^",
"||mastodon.nz^",
"||mastodon.online^",
"||mastodon.scot^",
"||mastodon.sdf.org^",
"||mastodon.social^",
"||mastodon.top^",
"||mastodon.uno^",
"||mastodon.world^",
"||mastodon.zaclys.com^",
"||mastodonapp.uk^",
"||mastodont.cat^",
"||mastodontech.de^",
"||mastodontti.fi^",
"||mastouille.fr^",
"||mathstodon.xyz^",
"||meow.social^",
"||metalhead.club^",
"||mindly.social^",
"||mstdn.ca^",
"||mstdn.jp^",
"||mstdn.party^",
"||mstdn.social^",
"||muenchen.social^",
"||muenster.im^",
"||nerdculture.de^",
"||newsie.social^",
"||noc.social^",
"||norden.social^",
"||nrw.social^",
"||o3o.ca^",
"||ohai.social^",
"||pewtix.com^",
"||phpc.social^",
"||piaille.fr^",
"||pol.social^",
"||qdon.space^",
"||ravenation.club^",
"||rollenspiel.social^",
"||ruby.social^",
"||ruhr.social^",
"||sfba.social^",
"||socel.net^",
"||social.anoxinon.de^",
"||social.cologne^",
"||social.dev-wiki.de^",
"||social.linux.pizza^",
"||social.politicaconciencia.org^",
"||social.vivaldi.net^",
"||sself.co^",
"||sueden.social^",
"||techhub.social^",
"||theblower.au^",
"||tkz.one^",
"||toot.aquilenet.fr^",
"||toot.community^",
"||toot.funami.tech^",
"||toot.wales^",
"||troet.cafe^",
"||uiuxdev.social^",
"||union.place^",
"||universeodon.com^",
"||urbanists.social^",
"||vocalodon.net^",
"||wxw.moe^",
"||xarxa.cloud^",
},
}, { }, {
ID: "minecraft", ID: "minecraft",
Name: "Minecraft", Name: "Minecraft",

View File

@@ -20,6 +20,7 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe" "github.com/google/renameio/maybe"
"golang.org/x/exp/slices"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
@@ -113,8 +114,8 @@ type configuration struct {
// An active session is automatically refreshed once a day. // An active session is automatically refreshed once a day.
WebSessionTTLHours uint32 `yaml:"web_session_ttl"` WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
DNS dnsConfig `yaml:"dns"` DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"` TLS tlsConfiguration `yaml:"tls"`
// Filters reflects the filters from [filtering.Config]. It's cloned to the // Filters reflects the filters from [filtering.Config]. It's cloned to the
// config used in the filtering module at the startup. Afterwards it's // config used in the filtering module at the startup. Afterwards it's
@@ -199,7 +200,8 @@ type dnsConfig struct {
UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"` UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"`
} }
type tlsConfigSettings struct { // tlsConfiguration is the on-disk TLS configuration.
type tlsConfiguration struct {
Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DoT/DoH/HTTPS) status Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DoT/DoH/HTTPS) status
ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
ForceHTTPS bool `yaml:"force_https" json:"force_https"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect ForceHTTPS bool `yaml:"force_https" json:"force_https"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
@@ -223,6 +225,29 @@ type tlsConfigSettings struct {
dnsforward.TLSConfig `yaml:",inline" json:",inline"` dnsforward.TLSConfig `yaml:",inline" json:",inline"`
} }
// cloneForEncoding returns a clone of c with all top-level fields of c and all
// exported and YAML-encoded fields of c.TLSConfig cloned.
//
// TODO(a.garipov): This is better than races, but still not good enough.
func (c *tlsConfiguration) cloneForEncoding() (cloned *tlsConfiguration) {
if c == nil {
return nil
}
v := *c
cloned = &v
cloned.TLSConfig = dnsforward.TLSConfig{
CertificateChain: c.CertificateChain,
PrivateKey: c.PrivateKey,
CertificatePath: c.CertificatePath,
PrivateKeyPath: c.PrivateKeyPath,
OverrideTLSCiphers: slices.Clone(c.OverrideTLSCiphers),
StrictSNICheck: c.StrictSNICheck,
}
return cloned
}
// config is the global configuration structure. // config is the global configuration structure.
// //
// TODO(a.garipov, e.burkov): This global is awful and must be removed. // TODO(a.garipov, e.burkov): This global is awful and must be removed.
@@ -273,7 +298,7 @@ var config = &configuration{
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true, UsePrivateRDNS: true,
}, },
TLS: tlsConfigSettings{ TLS: tlsConfiguration{
PortHTTPS: defaultPortHTTPS, PortHTTPS: defaultPortHTTPS,
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
PortDNSOverQUIC: defaultPortQUIC, PortDNSOverQUIC: defaultPortQUIC,
@@ -442,7 +467,7 @@ func (c *configuration) write() (err error) {
} }
if Context.tls != nil { if Context.tls != nil {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
config.TLS = tlsConf config.TLS = tlsConf
} }

View File

@@ -154,7 +154,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
return nil return nil
} }
tlsConf := &tlsConfigSettings{} tlsConf := &tlsConfiguration{}
Context.tls.WriteDiskConfig(tlsConf) Context.tls.WriteDiskConfig(tlsConf)
canUpdate := true canUpdate := true
@@ -172,7 +172,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
// tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration // tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration
// indicates that privileged ports are used. // indicates that privileged ports are used.
func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) { func tlsConfUsesPrivilegedPorts(c *tlsConfiguration) (ok bool) {
return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024) return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
} }

View File

@@ -205,7 +205,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
OnDNSRequest: onDNSRequest, OnDNSRequest: onDNSRequest,
} }
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled { if tlsConf.Enabled {
newConf.TLSConfig = tlsConf.TLSConfig newConf.TLSConfig = tlsConf.TLSConfig
@@ -250,7 +250,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
return newConf, nil return newConf, nil
} }
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfiguration) (dnscc dnsforward.DNSCryptConfig, err error) {
if tlsConf.DNSCryptConfigFile == "" { if tlsConf.DNSCryptConfigFile == "" {
return dnscc, errors.Error("no dnscrypt_config_file") return dnscc, errors.Error("no dnscrypt_config_file")
} }
@@ -288,7 +288,7 @@ type dnsEncryption struct {
} }
func getDNSEncryption() (de dnsEncryption) { func getDNSEncryption() (de dnsEncryption) {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)

View File

@@ -512,10 +512,9 @@ func run(opts options, clientBuildFS fs.FS) {
} }
config.Users = nil config.Users = nil
Context.tls, err = newTLSManager(config.TLS) Context.tls, err = newTLSManager(&config.TLS)
if err != nil { if err != nil {
log.Error("initializing tls: %s", err) log.Fatalf("initializing tls: %s", err)
onConfigModified()
} }
Context.web, err = initWeb(opts, clientBuildFS) Context.web, err = initWeb(opts, clientBuildFS)
@@ -818,7 +817,7 @@ func printWebAddrs(proto, addr string, port, betaPort int) {
// printHTTPAddresses prints the IP addresses which user can use to access the // printHTTPAddresses prints the IP addresses which user can use to access the
// admin interface. proto is either schemeHTTP or schemeHTTPS. // admin interface. proto is either schemeHTTP or schemeHTTPS.
func printHTTPAddresses(proto string) { func printHTTPAddresses(proto string) {
tlsConf := tlsConfigSettings{} tlsConf := tlsConfiguration{}
if Context.tls != nil { if Context.tls != nil {
Context.tls.WriteDiskConfig(&tlsConf) Context.tls.WriteDiskConfig(&tlsConf)
} }

View File

@@ -32,7 +32,11 @@ func setupDNSIPs(t testing.TB) {
}, },
} }
Context.tls = &tlsManager{} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
} }
func TestHandleMobileConfigDoH(t *testing.T) { func TestHandleMobileConfigDoH(t *testing.T) {
@@ -65,7 +69,11 @@ func TestHandleMobileConfigDoH(t *testing.T) {
oldTLSConf := Context.tls oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf }) t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil) r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -137,7 +145,11 @@ func TestHandleMobileConfigDoT(t *testing.T) {
oldTLSConf := Context.tls oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf }) t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}} var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -8,53 +8,46 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net/http"
"os" "os"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
) )
// tlsManager contains the current configuration and state of AdGuard Home TLS // tlsManager contains the current configuration and state of AdGuard Home TLS
// encryption. // encryption.
type tlsManager struct { type tlsManager struct {
// status is the current status of the configuration. It is never nil. // mu protects all fields.
status *tlsConfigStatus mu *sync.RWMutex
// certLastMod is the last modification time of the certificate file. // certLastMod is the last modification time of the certificate file.
certLastMod time.Time certLastMod time.Time
confLock sync.Mutex // status is the current status of the configuration. It is never nil.
conf tlsConfigSettings status *tlsConfigStatus
// conf is the current TLS configuration.
conf *tlsConfiguration
} }
// newTLSManager initializes the manager of TLS configuration. m is always // newTLSManager initializes the TLS configuration.
// non-nil while any returned error indicates that the TLS configuration isn't func newTLSManager(conf *tlsConfiguration) (m *tlsManager, err error) {
// valid. Thus TLS may be initialized later, e.g. via the web UI.
func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
m = &tlsManager{ m = &tlsManager{
status: &tlsConfigStatus{}, status: &tlsConfigStatus{},
mu: &sync.RWMutex{},
conf: conf, conf: conf,
} }
if m.conf.Enabled { if m.conf.Enabled {
err = m.load() err = m.load()
if err != nil { if err != nil {
m.conf.Enabled = false return nil, err
return m, err
} }
m.setCertFileTime() m.setCertFileTime()
@@ -63,9 +56,19 @@ func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
return m, nil return m, nil
} }
// confForEncoding returns a partial clone of the current TLS configuration. It
// is safe for concurrent use.
func (m *tlsManager) confForEncoding() (conf *tlsConfiguration) {
m.mu.RLock()
defer m.mu.RUnlock()
return m.conf.cloneForEncoding()
}
// load reloads the TLS configuration from files or data from the config file. // load reloads the TLS configuration from files or data from the config file.
// m.mu is expected to be locked for writing.
func (m *tlsManager) load() (err error) { func (m *tlsManager) load() (err error) {
err = loadTLSConf(&m.conf, m.status) err = loadTLSConf(m.conf, m.status)
if err != nil { if err != nil {
return fmt.Errorf("loading config: %w", err) return fmt.Errorf("loading config: %w", err)
} }
@@ -74,14 +77,12 @@ func (m *tlsManager) load() (err error) {
} }
// WriteDiskConfig - write config // WriteDiskConfig - write config
func (m *tlsManager) WriteDiskConfig(conf *tlsConfigSettings) { func (m *tlsManager) WriteDiskConfig(conf *tlsConfiguration) {
m.confLock.Lock() *conf = *m.confForEncoding()
*conf = m.conf
m.confLock.Unlock()
} }
// setCertFileTime sets t.certLastMod from the certificate. If there are // setCertFileTime sets t.certLastMod from the certificate. If there are
// errors, setCertFileTime logs them. // errors, setCertFileTime logs them. mu is expected to be locked for writing.
func (m *tlsManager) setCertFileTime() { func (m *tlsManager) setCertFileTime() {
if len(m.conf.CertificatePath) == 0 { if len(m.conf.CertificatePath) == 0 {
return return
@@ -101,27 +102,22 @@ func (m *tlsManager) setCertFileTime() {
func (m *tlsManager) start() { func (m *tlsManager) start() {
m.registerWebHandlers() m.registerWebHandlers()
m.confLock.Lock()
tlsConf := m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
Context.web.TLSConfigChanged(context.Background(), tlsConf) Context.web.TLSConfigChanged(context.Background(), m.confForEncoding())
} }
// reload updates the configuration and restarts t. // reload updates the configuration and restarts m.
func (m *tlsManager) reload() { func (m *tlsManager) reload() {
m.confLock.Lock() m.mu.Lock()
tlsConf := m.conf defer m.mu.Unlock()
m.confLock.Unlock()
if !tlsConf.Enabled || len(tlsConf.CertificatePath) == 0 { if !m.conf.Enabled || len(m.conf.CertificatePath) == 0 {
return return
} }
fi, err := os.Stat(tlsConf.CertificatePath) fi, err := os.Stat(m.conf.CertificatePath)
if err != nil { if err != nil {
log.Error("tls: %s", err) log.Error("tls: %s", err)
@@ -136,9 +132,7 @@ func (m *tlsManager) reload() {
log.Debug("tls: certificate file is modified") log.Debug("tls: certificate file is modified")
m.confLock.Lock()
err = m.load() err = m.load()
m.confLock.Unlock()
if err != nil { if err != nil {
log.Error("tls: reloading: %s", err) log.Error("tls: reloading: %s", err)
@@ -149,19 +143,15 @@ func (m *tlsManager) reload() {
_ = reconfigureDNSServer() _ = reconfigureDNSServer()
m.confLock.Lock()
tlsConf = m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
Context.web.TLSConfigChanged(context.Background(), tlsConf) Context.web.TLSConfigChanged(context.Background(), m.conf)
} }
// loadTLSConf loads and validates the TLS configuration. The returned error is // loadTLSConf loads and validates the TLS configuration. The returned error is
// also set in status.WarningValidation. // also set in status.WarningValidation.
func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error) { func loadTLSConf(tlsConf *tlsConfiguration, status *tlsConfigStatus) (err error) {
defer func() { defer func() {
if err != nil { if err != nil {
status.WarningValidation = err.Error() status.WarningValidation = err.Error()
@@ -176,13 +166,10 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
tlsConf.PrivateKeyData = []byte(tlsConf.PrivateKey) tlsConf.PrivateKeyData = []byte(tlsConf.PrivateKey)
if tlsConf.CertificatePath != "" { if tlsConf.CertificatePath != "" {
if tlsConf.CertificateChain != "" { err = loadCert(tlsConf)
return errors.Error("certificate data and file can't be set together")
}
tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
if err != nil { if err != nil {
return fmt.Errorf("reading cert file: %w", err) // Don't wrap the error, since it's informative enough as is.
return err
} }
// Set status.ValidCert to true to signal the frontend that the // Set status.ValidCert to true to signal the frontend that the
@@ -191,13 +178,10 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
} }
if tlsConf.PrivateKeyPath != "" { if tlsConf.PrivateKeyPath != "" {
if tlsConf.PrivateKey != "" { err = loadPKey(tlsConf)
return errors.Error("private key data and file can't be set together")
}
tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
if err != nil { if err != nil {
return fmt.Errorf("reading key file: %w", err) // Don't wrap the error, since it's informative enough as is.
return err
} }
status.ValidKey = true status.ValidKey = true
@@ -216,278 +200,29 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
return nil return nil
} }
// tlsConfigStatus contains the status of a certificate chain and key pair. // loadCert loads the certificate from file, if necessary.
type tlsConfigStatus struct { func loadCert(tlsConf *tlsConfiguration) (err error) {
// Subject is the subject of the first certificate in the chain. if tlsConf.CertificateChain != "" {
Subject string `json:"subject,omitempty"` return errors.Error("certificate data and file can't be set together")
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfig is the TLS configuration and status response.
type tlsConfig struct {
*tlsConfigStatus `json:",inline"`
tlsConfigSettingsExt `json:",inline"`
}
// tlsConfigSettingsExt is used to (un)marshal the PrivateKeySaved field to
// ensure that clients don't send and receive previously saved private keys.
type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
}
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
m.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
tlsConfigSettings: m.conf,
},
tlsConfigStatus: m.status,
} }
m.confLock.Unlock()
marshalTLS(w, r, data) tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
}
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err) return fmt.Errorf("reading cert file: %w", err)
return
} }
if setts.PrivateKeySaved { return nil
setts.PrivateKey = m.conf.PrivateKey
}
if setts.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(setts.PortHTTPS),
tcpPort(setts.PortDNSOverTLS),
tcpPort(setts.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(setts.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(setts.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
setts.PortHTTPS,
)
return
}
// Skip the error check, since we are only interested in the value of
// status.WarningValidation.
status := &tlsConfigStatus{}
_ = loadTLSConf(&setts.tlsConfigSettings, status)
resp := tlsConfig{
tlsConfigSettingsExt: setts,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
} }
func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatus) (restartHTTPS bool) { // loadPKey loads the private key from file, if necessary.
m.confLock.Lock() func loadPKey(tlsConf *tlsConfiguration) (err error) {
defer m.confLock.Unlock() if tlsConf.PrivateKey != "" {
return errors.Error("private key data and file cannot be set together")
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls config has changed, restarting https server")
restartHTTPS = true
} else {
log.Info("tls: config has not changed")
} }
// Note: don't do just `t.conf = data` because we must preserve all other members of t.conf tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.status = status
return restartHTTPS
}
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err) return fmt.Errorf("reading key file: %w", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.conf.PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
status := &tlsConfigStatus{}
err = loadTLSConf(&req.tlsConfigSettings, status)
if err != nil {
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
return
}
restartHTTPS := m.setConfig(req.tlsConfigSettings, status)
m.setCertFileTime()
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: m.status,
}
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartHTTPS {
go func() {
Context.web.TLSConfigChanged(context.Background(), req.tlsConfigSettings)
}()
}
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
} }
return nil return nil
@@ -704,61 +439,3 @@ func parsePrivateKey(der []byte) (key crypto.PrivateKey, typ string, err error)
return nil, "", errors.Error("tls: failed to parse private key") return nil, "", errors.Error("tls: failed to parse private key")
} }
// unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
data := tlsConfigSettingsExt{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return data, fmt.Errorf("failed to parse new TLS config json: %w", err)
}
if data.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(data.CertificateChain)
if err != nil {
return data, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
data.CertificateChain = string(cert)
if data.CertificatePath != "" {
return data, fmt.Errorf("certificate data and file can't be set together")
}
}
if data.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(data.PrivateKey)
if err != nil {
return data, fmt.Errorf("failed to base64-decode private key: %w", err)
}
data.PrivateKey = string(key)
if data.PrivateKeyPath != "" {
return data, fmt.Errorf("private key data and file can't be set together")
}
}
return data, nil
}
func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) {
if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = encoded
}
if data.PrivateKey != "" {
data.PrivateKeySaved = true
data.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, data)
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

362
internal/home/tlshttp.go Normal file
View File

@@ -0,0 +1,362 @@
package home
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
)
// Encryption Settings HTTP API
// tlsConfigStatus contains the status of a certificate chain and key pair.
type tlsConfigStatus struct {
// Subject is the subject of the first certificate in the chain.
Subject string `json:"subject,omitempty"`
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfigResp is the TLS configuration and status response.
type tlsConfigResp struct {
*tlsConfigStatus
*tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// tlsConfigReq is the TLS configuration request.
type tlsConfigReq struct {
tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
var resp *tlsConfigResp
func() {
m.mu.RLock()
defer m.mu.RUnlock()
resp = &tlsConfigResp{
tlsConfigStatus: m.status,
tlsConfiguration: m.conf.cloneForEncoding(),
}
}()
marshalTLS(w, r, resp)
}
// handleTLSValidate is the handler for the POST /control/tls/validate HTTP API.
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
// Skip the error check, since we are only interested in the value of
// resl.tlsConfigStatus.WarningValidation.
_ = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
marshalTLS(w, r, resp)
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
}
return nil
}
// handleTLSConfigure is the handler for the POST /control/tls/configure HTTP
// API.
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
err = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
if err != nil {
marshalTLS(w, r, resp)
return
}
restartRequired := m.setConf(resp)
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp.tlsConfiguration = m.confForEncoding()
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartRequired {
go func() {
Context.web.TLSConfigChanged(context.Background(), resp.tlsConfiguration)
}()
}
}
// setConf sets the necessary values from the new configuration.
func (m *tlsManager) setConf(newConf *tlsConfigResp) (restartRequired bool) {
m.mu.Lock()
defer m.mu.Unlock()
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls: config has changed, restarting https server")
restartRequired = true
} else {
log.Info("tls: config has not changed")
}
// Do not just write "m.conf = *newConf.tlsConfiguration", because all other
// members of m.conf must be preserved.
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.setCertFileTime()
m.status = newConf.tlsConfigStatus
return restartRequired
}
// marshalTLS handles Base64-encoded certificates transparently.
func marshalTLS(w http.ResponseWriter, r *http.Request, conf *tlsConfigResp) {
if conf.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(conf.CertificateChain))
conf.CertificateChain = encoded
}
if conf.PrivateKey != "" {
conf.PrivateKeySaved = true
conf.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, conf)
}
// unmarshalTLS handles Base64-encoded certificates transparently.
func unmarshalTLS(r *http.Request) (req *tlsConfigReq, err error) {
req = &tlsConfigReq{}
err = json.NewDecoder(r.Body).Decode(req)
if err != nil {
return nil, fmt.Errorf("parsing tls config: %w", err)
}
if req.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(req.CertificateChain)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
req.CertificateChain = string(cert)
if req.CertificatePath != "" {
return nil, fmt.Errorf("certificate data and file can't be set together")
}
}
if req.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode private key: %w", err)
}
req.PrivateKey = string(key)
if req.PrivateKeyPath != "" {
return nil, fmt.Errorf("private key data and file can't be set together")
}
}
return req, nil
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

View File

@@ -143,7 +143,7 @@ func webCheckPortAvailable(port int) (ok bool) {
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server // TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
// if necessary. // if necessary.
func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) { func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf *tlsConfiguration) {
log.Debug("web: applying new tls configuration") log.Debug("web: applying new tls configuration")
web.conf.PortHTTPS = tlsConf.PortHTTPS web.conf.PortHTTPS = tlsConf.PortHTTPS
web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0) web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)

View File

@@ -6,14 +6,6 @@
## v0.107.20: API Changes
### `POST /control/cache_clear`
* The new `POST /control/cache_clear` HTTP API allows clearing the DNS cache.
## v0.107.17: API Changes ## v0.107.17: API Changes
### `GET /control/blocked_services/services` is deprecated ### `GET /control/blocked_services/services` is deprecated

View File

@@ -94,15 +94,6 @@
'responses': 'responses':
'200': '200':
'description': 'OK' 'description': 'OK'
'/cache_clear':
'post':
'tags':
- 'global'
'operationId': 'cacheClear'
'summary': 'Clear DNS cache'
'responses':
'200':
'description': 'OK'
'/test_upstream_dns': '/test_upstream_dns':
'post': 'post':
'tags': 'tags':

View File

@@ -227,9 +227,9 @@ gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/
# Apply stricter standards to new or somewhat refactored code. # Apply stricter standards to new or somewhat refactored code.
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\ gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
./internal/aghtest/ ./internal/dnsforward/ ./internal/filtering/rewrite/\ ./internal/aghtest/ ./internal/dnsforward/ ./internal/stats/\
./internal/stats/ ./internal/tools/ ./internal/updater/ ./internal/next/\ ./internal/tools/ ./internal/updater/ ./internal/next/ ./internal/version/\
./internal/version/ ./scripts/vetted-filters/ ./main.go ./scripts/vetted-filters/ ./main.go
ineffassign ./... ineffassign ./...

View File

@@ -74,11 +74,7 @@ func main() {
Name: f.Name, Name: f.Name,
CategoryID: cat, CategoryID: cat,
Homepage: f.Homepage, Homepage: f.Homepage,
// NOTE: The source URL in filters.json is not guaranteed to contain Source: f.SourceURL,
// the URL of the filtering rule list. So, use our mirror for the
// vetted blocklists, which are mostly guaranteed to be valid and
// available lists.
Source: f.DownloadURL,
} }
} }
@@ -118,11 +114,11 @@ type hlFilters struct {
// hlFiltersFilter is the JSON structure for a filter in the Hostlists Registry. // hlFiltersFilter is the JSON structure for a filter in the Hostlists Registry.
type hlFiltersFilter struct { type hlFiltersFilter struct {
DownloadURL string `json:"downloadUrl"` FilterID string `json:"filterId"`
FilterID string `json:"filterId"` Name string `json:"name"`
Homepage string `json:"homepage"` Homepage string `json:"homepage"`
Name string `json:"name"` SourceURL string `json:"sourceUrl"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
} }
// category returns the AdGuard Home category for this filter. If there is no // category returns the AdGuard Home category for this filter. If there is no