Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
618d0e596c | ||
|
|
fde9ea5cb1 | ||
|
|
03d9803238 | ||
|
|
bd64b8b014 | ||
|
|
67fe064fcf |
98
CHANGELOG.md
98
CHANGELOG.md
@@ -12,18 +12,95 @@ and this project adheres to
|
||||
## [Unreleased]
|
||||
|
||||
<!--
|
||||
## [v0.108.0] - TBA (APPROX.)
|
||||
## [v0.108.0] - TBA
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
## [v0.107.18] - 2022-11-16 (APPROX.)
|
||||
## [v0.107.21] - 2122-12-28 (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].
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to block popular Mastodon instances
|
||||
([AdguardTeam/HostlistsRegistry#100]).
|
||||
- The new `--update` command-line option, which allows updating AdGuard Home
|
||||
silently ([#4223]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Minor UI changes.
|
||||
|
||||
[#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
|
||||
|
||||
See also the [v0.107.18 GitHub milestone][ms-v0.107.18].
|
||||
|
||||
[ms-v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/milestone/52?closed=1
|
||||
-->
|
||||
### Fixed
|
||||
|
||||
- Crash on some systems when domains from system hosts files are processed
|
||||
([#5089]).
|
||||
|
||||
[#5089]: https://github.com/AdguardTeam/AdGuardHome/issues/5089
|
||||
|
||||
[ms-v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/milestone/54?closed=1
|
||||
|
||||
|
||||
|
||||
@@ -830,7 +907,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
|
||||
- Query log search now supports internationalized domains ([#3012]).
|
||||
- Internationalized domains are now shown decoded in the query log with the
|
||||
original encoded version shown in request details ([#3013]).
|
||||
- When /etc/hosts-type rules have several IPs for one host, all IPs are now
|
||||
- When `/etc/hosts`-type rules have several IPs for one host, all IPs are now
|
||||
returned instead of only the first one ([#1381]).
|
||||
- Property `rlimit_nofile` is now in the `os` object of the configuration
|
||||
file, together with the new `group` and `user` properties ([#2763]).
|
||||
@@ -1088,7 +1165,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
|
||||
- Hostname uniqueness validation in the DHCP server ([#2952]).
|
||||
- Hostname generating for DHCP clients which don't provide their own ([#2723]).
|
||||
- New flag `--no-etc-hosts` to disable client domain name lookups in the
|
||||
operating system's /etc/hosts files ([#1947]).
|
||||
operating system's `/etc/hosts` files ([#1947]).
|
||||
- The ability to set up custom upstreams to resolve PTR queries for local
|
||||
addresses and to disable the automatic resolving of clients' addresses
|
||||
([#2704]).
|
||||
@@ -1405,11 +1482,14 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...HEAD
|
||||
[v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.21...HEAD
|
||||
[v0.107.21]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.20...v0.107.21
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.20...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.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.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.4'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
@@ -322,7 +322,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.4'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||
# is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -337,4 +337,4 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.4'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:5.4'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
||||
@@ -392,6 +392,7 @@
|
||||
"encryption_issuer": "المصدر",
|
||||
"encryption_hostnames": "اسم المستضيف",
|
||||
"encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟",
|
||||
"encryption_warning": "تحذير",
|
||||
"topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير</0>.",
|
||||
"topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير</0>.",
|
||||
"form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Выдавец",
|
||||
"encryption_hostnames": "Імёны хастоў",
|
||||
"encryption_reset": "Вы ўпэўнены, што хочаце скінуць налады шыфравання?",
|
||||
"encryption_warning": "Папярэджанне",
|
||||
"topline_expiring_certificate": "Ваш SSL-сертыфікат хутка мінае. Абновіце <0>Налады шыфравання</0>.",
|
||||
"topline_expired_certificate": "Ваш SSL-сертыфікат мінуў. Абновіце <0>Налады шыфравання</0>.",
|
||||
"form_error_port_range": "Увядзіце нумар порта з інтэрвалу 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Бяспечны інтэрнэт",
|
||||
"served_from_cache": "{{value}} <i>(атрымана з кэша)</i>",
|
||||
"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": "Ачысціць кэш"
|
||||
}
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"encryption_issuer": "Изпълнител",
|
||||
"encryption_hostnames": "Имена на хоста",
|
||||
"encryption_reset": "Сигурни ли сте че искате да изтриете настройките за криптиране?",
|
||||
"encryption_warning": "Внимание",
|
||||
"topline_expiring_certificate": "Вашият SSL сертификат изтича. Обнови <0>Настройки за криптиране</0>.",
|
||||
"topline_expired_certificate": "Вашият SSL сертификат е изтекъл. Обнови <0>Настройки за криптиране</0>.",
|
||||
"form_error_port_range": "Въведете порт в диапазона 80-65535",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Vydavatel",
|
||||
"encryption_hostnames": "Názvy hostitelů",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Bezpečné prohlížení",
|
||||
"served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
|
||||
"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ěť"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Udsteder",
|
||||
"encryption_hostnames": "Værtsnavne",
|
||||
"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_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.",
|
||||
"form_error_port_range": "Angiv portnummer i intervallet 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Sikker Browsing",
|
||||
"served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Ausgestellt von",
|
||||
"encryption_hostnames": "Hostnamen",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Internetsicherheit",
|
||||
"served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip4_format": "Invalid IPv4 address",
|
||||
"form_error_ip4_range_start_format": "Invalid IPv4 address of the range start",
|
||||
"form_error_ip4_range_end_format": "Invalid IPv4 address of the range end",
|
||||
"form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway",
|
||||
"form_error_ip6_format": "Invalid IPv6 address",
|
||||
"form_error_ip_format": "Invalid IP address",
|
||||
@@ -51,9 +49,8 @@
|
||||
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Must be lower than range start",
|
||||
"greater_range_start_error": "Must be greater than range start",
|
||||
"greater_range_end_error": "Must be greater than range end",
|
||||
"subnet_error": "Addresses must be in one subnet",
|
||||
"gateway_or_subnet_invalid": "Subnet mask invalid",
|
||||
"gateway_or_subnet_invalid": "Invalid subnet mask",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
@@ -393,6 +390,7 @@
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"encryption_warning": "Warning",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
|
||||
"form_error_port_range": "Enter port number in the range of 80-65535",
|
||||
@@ -637,5 +635,8 @@
|
||||
"safe_browsing": "Safe Browsing",
|
||||
"served_from_cache": "{{value}} <i>(served from cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Emisor",
|
||||
"encryption_hostnames": "Nombres de hosts",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navegación segura",
|
||||
"served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
|
||||
"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é"
|
||||
}
|
||||
|
||||
@@ -361,6 +361,7 @@
|
||||
"encryption_issuer": "صادر کننده",
|
||||
"encryption_hostnames": "نام میزبان",
|
||||
"encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟",
|
||||
"encryption_warning": "هشدار",
|
||||
"topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
|
||||
"topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
|
||||
"form_error_port_range": "مقدار پورت را در محدوده 80-65535 وارد کنید",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Toimittaja",
|
||||
"encryption_hostnames": "Isäntänimet",
|
||||
"encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
|
||||
"encryption_warning": "Varoitus",
|
||||
"topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
|
||||
"topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
|
||||
"form_error_port_range": "Syötä portti väliltä 80-65535",
|
||||
@@ -542,8 +543,8 @@
|
||||
"descr": "Kuvaus",
|
||||
"whois": "WHOIS",
|
||||
"filtering_rules_learn_more": "<0>Lue lisää</0> omien hosts-listojesi luonnista.",
|
||||
"blocked_by_response": "Vastauksen sisältämän CNAME:n tai IP:n estämä",
|
||||
"blocked_by_cname_or_ip": "CNAME:n tai IP:n estämä",
|
||||
"blocked_by_response": "Estetty vastauksen CNAME:n tai IP:n perusteella",
|
||||
"blocked_by_cname_or_ip": "Estetty CNAME:n tai IP:n perusteella",
|
||||
"try_again": "Yritä uudelleen",
|
||||
"domain_desc": "Syötä korvattava verkkotunnus tai jokerimerkki.",
|
||||
"example_rewrite_domain": "korvaa vain tämän verkkotunnuksen vastaukset",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Turvallinen selaus",
|
||||
"served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Émetteur",
|
||||
"encryption_hostnames": "Noms d'hôte",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navigation sécurisée",
|
||||
"served_from_cache": "{{value}} <i>(depuis le cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Izdavač",
|
||||
"encryption_hostnames": "Nazivi računala",
|
||||
"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_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
|
||||
"form_error_port_range": "Unesite broj porta od 80 do 65536",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Sigurno surfanje",
|
||||
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Kibocsátó",
|
||||
"encryption_hostnames": "Hosztnevek",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Biztonságos böngészés",
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Penerbit",
|
||||
"encryption_hostnames": "Nama host",
|
||||
"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_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
|
||||
"form_error_port_range": "Masukkan nomor port di kisaran 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Penjelajahan Aman",
|
||||
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Emittente",
|
||||
"encryption_hostnames": "Nomi host",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navigazione Sicura",
|
||||
"served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "発行者",
|
||||
"encryption_hostnames": "ホスト名",
|
||||
"encryption_reset": "暗号化設定をリセットして良いですか?",
|
||||
"encryption_warning": "警告",
|
||||
"topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
|
||||
"topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
|
||||
"form_error_port_range": "80〜65535 の範囲内でポート番号を入力してください",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "セーフブラウジング",
|
||||
"served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
|
||||
"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": "キャッシュをクリアする"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "발행자",
|
||||
"encryption_hostnames": "호스트 이름",
|
||||
"encryption_reset": "암호화 설정을 재설정하시겠습니까?",
|
||||
"encryption_warning": "경고",
|
||||
"topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "세이프 브라우징",
|
||||
"served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
|
||||
"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": "캐시 지우기"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Uitgever",
|
||||
"encryption_hostnames": "Hostnamen",
|
||||
"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_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
|
||||
"form_error_port_range": "Poortnummer invoeren tussen 80 en 65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Veilig browsen",
|
||||
"served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -373,6 +373,7 @@
|
||||
"encryption_issuer": "Utsteder",
|
||||
"encryption_hostnames": "Vertsnavn",
|
||||
"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_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger</0>.",
|
||||
"form_error_port_range": "Skriv inn et portnummer i området 80-65535",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Zgłaszający",
|
||||
"encryption_hostnames": "Nazwy hostów",
|
||||
"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_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
|
||||
"form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Bezpieczne przeglądanie",
|
||||
"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",
|
||||
"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ą"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Emissor",
|
||||
"encryption_hostnames": "Nomes dos servidores",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navegação segura",
|
||||
"served_from_cache": "{{value}} <i>(servido do cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Emissor",
|
||||
"encryption_hostnames": "Nomes dos servidores",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navegação segura",
|
||||
"served_from_cache": "{{value}} <i>(servido do cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Emitent",
|
||||
"encryption_hostnames": "Nume de host",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Navigare în siguranță",
|
||||
"served_from_cache": "{{value}} <i>(furnizat din cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Издатель",
|
||||
"encryption_hostnames": "Имена хостов",
|
||||
"encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
|
||||
"encryption_warning": "Предупреждение",
|
||||
"topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
|
||||
"topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.",
|
||||
"form_error_port_range": "Введите номер порта из интервала 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Безопасный интернет",
|
||||
"served_from_cache": "{{value}} <i>(получено из кеша)</i>",
|
||||
"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": "Очистить кеш"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Vydavateľ",
|
||||
"encryption_hostnames": "Názvy hostiteľov",
|
||||
"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_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.",
|
||||
"form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Bezpečné prehliadanie",
|
||||
"served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
|
||||
"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äť"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Izdajatelj",
|
||||
"encryption_hostnames": "Imena gostiteljev",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Varno brskanje",
|
||||
"served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Izdavač",
|
||||
"encryption_hostnames": "Imena hostova",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Sigurno pregledanje",
|
||||
"served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Utgivare",
|
||||
"encryption_hostnames": "Värdnamn",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Säker surfning",
|
||||
"served_from_cache": "{{value}} <i>(levereras från cache)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@
|
||||
"encryption_issuer": "ผู้ออกใบรับรอง:",
|
||||
"encryption_hostnames": "ชื่อโฮส",
|
||||
"encryption_reset": "คุณแน่ใจนะว่าจะล้างค่าการเข้ารหัส?",
|
||||
"encryption_warning": "คำเตือน",
|
||||
"topline_expiring_certificate": "ใบรับรอง SSL ของคุณกำลังจะหมดอายุ กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
|
||||
"topline_expired_certificate": "ใบรับรอง SSL ของคุณหมดอายุแล้ว กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
|
||||
"form_error_port_unsafe": "เป็นพอร์ทที่ไม่ปลอดภัย",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Sağlayan",
|
||||
"encryption_hostnames": "Ana makine adları",
|
||||
"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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Güvenli Gezinti",
|
||||
"served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@
|
||||
"encryption_redirect": "Автоматично перенаправляти на HTTPS",
|
||||
"encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.",
|
||||
"encryption_https": "Порт HTTPS",
|
||||
"encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.",
|
||||
"encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також сервер DNS-over-HTTPS буде доступний за адресою '/dns-query'.",
|
||||
"encryption_dot": "Порт DNS-over-TLS",
|
||||
"encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.",
|
||||
"encryption_doq": "Порт DNS-over-QUIC",
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Видавець",
|
||||
"encryption_hostnames": "Назви вузлів",
|
||||
"encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
|
||||
"encryption_warning": "Попередження",
|
||||
"topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
|
||||
"topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
|
||||
"form_error_port_range": "Введіть значення порту в діапазоні 80−65535",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Безпечний перегляд",
|
||||
"served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
|
||||
"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": "Очистити кеш"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "Phát hành",
|
||||
"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_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_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",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "Duyệt web an toàn",
|
||||
"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ự",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "颁发者",
|
||||
"encryption_hostnames": "主机名",
|
||||
"encryption_reset": "您确定想要重置加密设置?",
|
||||
"encryption_warning": "警告",
|
||||
"topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
|
||||
"topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
|
||||
"form_error_port_range": "输入 80 - 65535 范围内的端口值",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "安全浏览",
|
||||
"served_from_cache": "{{value}}<i>(由缓存提供)</i>",
|
||||
"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": "清除缓存"
|
||||
}
|
||||
|
||||
@@ -381,6 +381,7 @@
|
||||
"encryption_issuer": "簽發者",
|
||||
"encryption_hostnames": "主機名稱",
|
||||
"encryption_reset": "您確定要重設加密設定嗎?",
|
||||
"encryption_warning": "警告",
|
||||
"topline_expiring_certificate": "您的 SSL 憑證即將到期。請前往<0>加密設定</0>更新。",
|
||||
"topline_expired_certificate": "您的 SSL 憑證已到期。請前往<0>加密設定</0>更新。",
|
||||
"form_error_port_range": "輸入範圍 80-65535 中的值",
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
"encryption_issuer": "簽發者",
|
||||
"encryption_hostnames": "主機名稱",
|
||||
"encryption_reset": "您確定您想要重置加密設定嗎?",
|
||||
"encryption_warning": "警告",
|
||||
"topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
|
||||
"topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
|
||||
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
|
||||
@@ -637,5 +638,8 @@
|
||||
"safe_browsing": "安全瀏覽",
|
||||
"served_from_cache": "{{value}} <i>(由快取提供)</i>",
|
||||
"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": "清除快取"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import apiClient from '../api/Api';
|
||||
import { splitByNewLine } from '../helpers/helpers';
|
||||
@@ -19,6 +20,22 @@ 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 setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE');
|
||||
export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');
|
||||
|
||||
@@ -593,6 +593,14 @@ class Api {
|
||||
};
|
||||
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();
|
||||
|
||||
@@ -74,7 +74,6 @@ const FormDHCPv4 = ({
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.subnet_mask)}
|
||||
validate={[
|
||||
validateIpv4,
|
||||
validateRequired,
|
||||
validateGatewaySubnetMask,
|
||||
]}
|
||||
@@ -97,7 +96,6 @@ const FormDHCPv4 = ({
|
||||
placeholder={t(ipv4placeholders.range_start)}
|
||||
validate={[
|
||||
validateIpv4,
|
||||
validateGatewaySubnetMask,
|
||||
validateIpForGatewaySubnetMask,
|
||||
]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
@@ -113,7 +111,6 @@ const FormDHCPv4 = ({
|
||||
validate={[
|
||||
validateIpv4,
|
||||
validateIpv4RangeEnd,
|
||||
validateGatewaySubnetMask,
|
||||
validateIpForGatewaySubnetMask,
|
||||
]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
|
||||
@@ -2,10 +2,12 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { renderInputField, toNumber, CheckboxField } from '../../../../helpers/form';
|
||||
import { CACHE_CONFIG_FIELDS, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
|
||||
import { replaceZeroWithEmptyString } from '../../../../helpers/helpers';
|
||||
import { clearDnsCache } from '../../../../actions/dnsConfig';
|
||||
|
||||
const INPUTS_FIELDS = [
|
||||
{
|
||||
@@ -32,6 +34,7 @@ const Form = ({
|
||||
handleSubmit, submitting, invalid,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { processingSetConfig } = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||
const {
|
||||
@@ -40,6 +43,12 @@ const Form = ({
|
||||
|
||||
const minExceedsMax = cache_ttl_min > cache_ttl_max;
|
||||
|
||||
const handleClearCache = () => {
|
||||
if (window.confirm(t('confirm_dns_cache_clear'))) {
|
||||
dispatch(clearDnsCache());
|
||||
}
|
||||
};
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
{INPUTS_FIELDS.map(({
|
||||
@@ -97,6 +106,13 @@ const Form = ({
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-secondary btn-standard form__button"
|
||||
onClick={handleClearCache}
|
||||
>
|
||||
<Trans>clear_cache</Trans>
|
||||
</button>
|
||||
</form>;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,26 @@ const clearFields = (change, setTlsConfig, t) => {
|
||||
}
|
||||
};
|
||||
|
||||
const validationMessage = (warningValidation, isWarning) => {
|
||||
if (!warningValidation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isWarning) {
|
||||
return (
|
||||
<div className="col-12">
|
||||
<p><Trans>encryption_warning</Trans>: {warningValidation}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-12">
|
||||
<p className="text-danger">{warningValidation}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
@@ -95,6 +115,8 @@ let Form = (props) => {
|
||||
|| !valid_cert
|
||||
|| !valid_pair;
|
||||
|
||||
const isWarning = valid_key && valid_cert && valid_pair;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
@@ -382,11 +404,7 @@ let Form = (props) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{warning_validation && (
|
||||
<div className="col-12">
|
||||
<p className="text-danger">{warning_validation}</p>
|
||||
</div>
|
||||
)}
|
||||
{validationMessage(warning_validation, isWarning)}
|
||||
</div>
|
||||
|
||||
<div className="btn-list mt-2">
|
||||
|
||||
@@ -26,199 +26,199 @@ export default {
|
||||
"name": "1Hosts (Lite)",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://badmojr.github.io/1Hosts/",
|
||||
"source": "https://badmojr.gitlab.io/1hosts/Lite/adblock.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_24.txt"
|
||||
},
|
||||
"CHN_adrules": {
|
||||
"name": "CHN: AdRules DNS List",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/Cats-Team/AdRules",
|
||||
"source": "https://raw.githubusercontent.com/Cats-Team/AdRules/main/dns.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_29.txt"
|
||||
},
|
||||
"CHN_anti_ad": {
|
||||
"name": "CHN: anti-AD",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://anti-ad.net/",
|
||||
"source": "https://anti-ad.net/easylist.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_21.txt"
|
||||
},
|
||||
"IDN_abpindo": {
|
||||
"name": "IDN: ABPindo",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/ABPindo/indonesianadblockrules",
|
||||
"source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/aghome.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_22.txt"
|
||||
},
|
||||
"IRN_unwanted_iranian_domains": {
|
||||
"name": "IRN: PersianBlocker list",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/MasterKia/PersianBlocker",
|
||||
"source": "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlockerHosts.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.txt"
|
||||
},
|
||||
"ITA_filtri_dns": {
|
||||
"name": "ITA: Filtri-DNS",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://filtri-dns.ga/",
|
||||
"source": "https://filtri-dns.ga/filtri.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"
|
||||
},
|
||||
"KOR_list_kr": {
|
||||
"name": "KOR: List-KR DNS",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/List-KR/List-KR",
|
||||
"source": "https://github.com/List-KR/List-KR"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_25.txt"
|
||||
},
|
||||
"KOR_youslist": {
|
||||
"name": "KOR: YousList",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/yous/YousList",
|
||||
"source": "https://raw.githubusercontent.com/yous/YousList/master/hosts.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_15.txt"
|
||||
},
|
||||
"MKD_macedonian_pi_hole_blocklist": {
|
||||
"name": "MKD: Macedonian Pi-hole Blocklist",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/cchevy/macedonian-pi-hole-blocklist",
|
||||
"source": "https://raw.githubusercontent.com/cchevy/macedonian-pi-hole-blocklist/master/hosts.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_20.txt"
|
||||
},
|
||||
"NOR_dandelion_sprouts_anti_malware_list": {
|
||||
"name": "NOR: Dandelion Sprouts nordiske filtre",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/DandelionSprout/adfilt",
|
||||
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/NordicFiltersAdGuardHome.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_13.txt"
|
||||
},
|
||||
"POL_polish_filters_for_pi_hole": {
|
||||
"name": "POL: Polish filters for Pi hole",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://www.certyficate.it/",
|
||||
"source": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/hostfile.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_14.txt"
|
||||
},
|
||||
"SWE_frellwit_swedish_hosts_file": {
|
||||
"name": "SWE: Frellwit's Swedish Hosts File",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/lassekongo83/Frellwits-filter-lists/",
|
||||
"source": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Hosts-File.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_17.txt"
|
||||
},
|
||||
"TUR_turk_adlist": {
|
||||
"name": "TUR: turk-adlist",
|
||||
"categoryId": "regional",
|
||||
"homepage": "https://github.com/bkrucarci/turk-adlist",
|
||||
"source": "https://raw.githubusercontent.com/bkrucarci/turk-adlist/master/hosts"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_26.txt"
|
||||
},
|
||||
"VNM_abpvn": {
|
||||
"name": "VNM: ABPVN List",
|
||||
"categoryId": "regional",
|
||||
"homepage": "http://abpvn.com/",
|
||||
"source": "https://abpvn.com/android/abpvn.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_16.txt"
|
||||
},
|
||||
"adguard_dns_filter": {
|
||||
"name": "AdGuard DNS filter",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
|
||||
"source": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"
|
||||
},
|
||||
"adway_default_blocklist": {
|
||||
"name": "AdAway Default Blocklist",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/AdAway/adaway.github.io/",
|
||||
"source": "https://adaway.org/hosts.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt"
|
||||
},
|
||||
"curben_phishing_filter": {
|
||||
"name": "Phishing URL Blocklist (PhishTank and OpenPhish)",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://gitlab.com/malware-filter/phishing-filter",
|
||||
"source": "https://malware-filter.gitlab.io/malware-filter/phishing-filter-agh.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_30.txt"
|
||||
},
|
||||
"dan_pollocks_list": {
|
||||
"name": "Dan Pollock's List",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://someonewhocares.org/",
|
||||
"source": "https://someonewhocares.org/hosts/zero/hosts"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_4.txt"
|
||||
},
|
||||
"dandelion_sprouts_anti_malware_list": {
|
||||
"name": "Dandelion Sprout's Anti-Malware List",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/DandelionSprout/adfilt",
|
||||
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareAdGuardHome.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt"
|
||||
},
|
||||
"dandelion_sprouts_game_console_adblock_list": {
|
||||
"name": "Dandelion Sprout's Game Console Adblock List",
|
||||
"categoryId": "other",
|
||||
"homepage": "https://github.com/DandelionSprout/adfilt",
|
||||
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/GameConsoleAdblockList.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt"
|
||||
},
|
||||
"energized_spark": {
|
||||
"name": "Energized Spark",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://energized.pro/",
|
||||
"source": "https://block.energized.pro/spark/formats/filter"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_28.txt"
|
||||
},
|
||||
"nocoin_filter_list": {
|
||||
"name": "NoCoin Filter List",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
|
||||
"source": "https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/hosts.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_8.txt"
|
||||
},
|
||||
"notracking_hosts_blocklists": {
|
||||
"name": "The NoTracking blocklist",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/notracking/hosts-blocklists",
|
||||
"source": "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/adblock/adblock.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_32.txt"
|
||||
},
|
||||
"oisd_basic": {
|
||||
"name": "OISD Blocklist Basic",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://oisd.nl/",
|
||||
"source": "https://abp.oisd.nl/basic/"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_5.txt"
|
||||
},
|
||||
"oisd_full": {
|
||||
"name": "OISD Blocklist Full",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://oisd.nl/",
|
||||
"source": "https://abp.oisd.nl/"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt"
|
||||
},
|
||||
"perflyst_dandelion_sprout_smart_tv_blocklist_for_adguard_home": {
|
||||
"name": "Perflyst and Dandelion Sprout's Smart-TV Blocklist",
|
||||
"categoryId": "other",
|
||||
"homepage": "https://github.com/Perflyst/PiHoleBlocklist",
|
||||
"source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_7.txt"
|
||||
},
|
||||
"peter_lowe_list": {
|
||||
"name": "Peter Lowe's Blocklist",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://pgl.yoyo.org/adservers/",
|
||||
"source": "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus\u0026showintro=1\u0026mimetype=plaintext"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_3.txt"
|
||||
},
|
||||
"scam_blocklist_by_durablenapkin": {
|
||||
"name": "Scam Blocklist by DurableNapkin",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/durablenapkin/scamblocklist",
|
||||
"source": "https://raw.githubusercontent.com/durablenapkin/scamblocklist/master/adguard.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_10.txt"
|
||||
},
|
||||
"staklerware_indicators_list": {
|
||||
"name": "Stalkerware Indicators List",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/AssoEchap/stalkerware-indicators",
|
||||
"source": "https://raw.githubusercontent.com/AssoEchap/stalkerware-indicators/master/generated/hosts"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_31.txt"
|
||||
},
|
||||
"steven_blacks_list": {
|
||||
"name": "Steven Black's List",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/StevenBlack/hosts",
|
||||
"source": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_33.txt"
|
||||
},
|
||||
"the_big_list_of_hacked_malware_web_sites": {
|
||||
"name": "The Big List of Hacked Malware Web Sites",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
|
||||
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hosts"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt"
|
||||
},
|
||||
"urlhaus_filter_online": {
|
||||
"name": "Malicious URL Blocklist (URLHaus)",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://gitlab.com/malware-filter/urlhaus-filter",
|
||||
"source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"
|
||||
},
|
||||
"windowsspyblocker_hosts_spy_rules": {
|
||||
"name": "WindowsSpyBlocker - Hosts spy rules",
|
||||
"categoryId": "other",
|
||||
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
|
||||
"source": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt"
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +77,11 @@ export const validateNotInRange = (value, allValues) => {
|
||||
const { range_start, range_end } = allValues.v4;
|
||||
|
||||
if (range_start && validateIpv4(range_start)) {
|
||||
return 'form_error_ip4_range_start_format';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (range_end && validateIpv4(range_end)) {
|
||||
return 'form_error_ip4_range_end_format';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isAboveMin = range_start && ip4ToInt(value) >= ip4ToInt(range_start);
|
||||
@@ -94,14 +94,6 @@ 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;
|
||||
};
|
||||
|
||||
@@ -118,7 +110,7 @@ export const validateGatewaySubnetMask = (_, allValues) => {
|
||||
const { subnet_mask, gateway_ip } = allValues.v4;
|
||||
|
||||
if (validateIpv4(gateway_ip)) {
|
||||
return 'form_error_ip4_gateway_format';
|
||||
return 'gateway_or_subnet_invalid';
|
||||
}
|
||||
|
||||
return parseSubnetMask(subnet_mask) ? undefined : 'gateway_or_subnet_invalid';
|
||||
@@ -138,6 +130,10 @@ export const validateIpForGatewaySubnetMask = (value, allValues) => {
|
||||
gateway_ip, subnet_mask,
|
||||
} = allValues.v4;
|
||||
|
||||
if ((gateway_ip && validateIpv4(gateway_ip)) || (subnet_mask && validateIpv4(subnet_mask))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const subnetPrefix = parseSubnetMask(subnet_mask);
|
||||
|
||||
if (!isIpInCidr(value, `${gateway_ip}/${subnetPrefix}`)) {
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.46.2
|
||||
github.com/AdguardTeam/golibs v0.11.2
|
||||
github.com/AdguardTeam/dnsproxy v0.46.4
|
||||
github.com/AdguardTeam/golibs v0.11.3
|
||||
github.com/AdguardTeam/urlfilter v0.16.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.5
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/lucas-clemente/quic-go v0.29.2
|
||||
github.com/lucas-clemente/quic-go v0.31.0
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||
github.com/mdlayher/netlink v1.6.2
|
||||
// TODO(a.garipov): This package is deprecated; find a new one or use
|
||||
@@ -29,9 +29,9 @@ require (
|
||||
github.com/ti-mo/netfilter v0.4.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136
|
||||
golang.org/x/net v0.1.0
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/sys v0.2.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0
|
||||
@@ -47,16 +47,14 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // 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/marten-seemann/qpack v0.3.0 // 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/mdlayher/packet v1.0.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 // indirect
|
||||
github.com/onsi/gomega v1.22.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.5.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -65,5 +63,4 @@ require (
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
78
go.sum
78
go.sum
@@ -1,9 +1,9 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.46.2 h1:ZUKM713Ts5meYQqk6cJkUBMCFSWqFPXTgjXkN4RI1Vo=
|
||||
github.com/AdguardTeam/dnsproxy v0.46.2/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA=
|
||||
github.com/AdguardTeam/dnsproxy v0.46.4 h1:/+wnTG0q2TkGQyA1PeSsjv4/f5ZprGduKPSoOcG+rOU=
|
||||
github.com/AdguardTeam/dnsproxy v0.46.4/go.mod h1:yYDMAH6ay2PxLcLtfVM3FUiyv/U9B/zYO+cIIssuJNU=
|
||||
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.11.2 h1:JbQB1Dg2JWStXgHh1QqBbOLWnP4t9oDjppoBH6TVXSE=
|
||||
github.com/AdguardTeam/golibs v0.11.2/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
|
||||
github.com/AdguardTeam/golibs v0.11.3 h1:Oif+REq2WLycQ2Xm3ZPmJdfftptss0HbGWbxdFaC310=
|
||||
github.com/AdguardTeam/golibs v0.11.3/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
|
||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||
github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg=
|
||||
github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
|
||||
@@ -25,6 +25,9 @@ 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/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
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/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=
|
||||
@@ -34,8 +37,6 @@ 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/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
|
||||
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/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
@@ -47,13 +48,6 @@ 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/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/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/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -66,15 +60,17 @@ 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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
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/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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/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/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -91,8 +87,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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.29.2 h1:O8Mt0O6LpvEW+wfC40vZdcw0DngwYzoxq5xULZNzSI8=
|
||||
github.com/lucas-clemente/quic-go v0.29.2/go.mod h1:g6/h9YMmLuU54tL1gW25uIi3VlBp3uv+sBihplIuskE=
|
||||
github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
|
||||
github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
|
||||
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/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
|
||||
@@ -124,19 +120,9 @@ 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/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/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.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
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.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
|
||||
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
|
||||
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/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
|
||||
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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -168,7 +154,6 @@ 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-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
|
||||
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=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
@@ -177,15 +162,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 h1:9bNbSKT4RPLEzne0Xh1v3NaNecsa1DKjkOuTbY6V9rI=
|
||||
golang.org/x/exp v0.0.0-20221026153819-32f3d567a233/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
|
||||
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/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.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -195,10 +178,8 @@ 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-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-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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
@@ -208,14 +189,11 @@ 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.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
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-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-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -224,13 +202,10 @@ 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-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-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-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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -238,7 +213,6 @@ 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-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-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-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -254,8 +228,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -269,7 +243,6 @@ 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-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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
@@ -278,26 +251,15 @@ 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-20191204190536-9bdfabe68543/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=
|
||||
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/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/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.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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -139,6 +139,8 @@ type HostsRecord struct {
|
||||
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
|
||||
if rec == nil {
|
||||
return other == nil
|
||||
} else if other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
|
||||
@@ -478,7 +480,11 @@ func (hc *HostsContainer) refresh() (err error) {
|
||||
return fmt.Errorf("refreshing : %w", err)
|
||||
}
|
||||
|
||||
if maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
|
||||
// hc.last is nil on the first refresh, so let that one through.
|
||||
//
|
||||
// TODO(a.garipov): Once https://github.com/golang/go/issues/56621 is
|
||||
// resolved, remove the first condition.
|
||||
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
|
||||
log.Debug("%s: no changes detected", hostsContainerPref)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
func defaultHostsPaths() (paths []string) {
|
||||
sysDir, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
log.Error("getting system directory: %s", err)
|
||||
log.Error("aghnet: getting system directory: %s", err)
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,6 @@ var (
|
||||
// the IP being static is available.
|
||||
const ErrNoStaticIPInfo errors.Error = "no information about static ip"
|
||||
|
||||
// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4].
|
||||
func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{127, 0, 0, 1}) }
|
||||
|
||||
// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6].
|
||||
func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) }
|
||||
|
||||
// IfaceHasStaticIP checks if interface is configured to have static IP address.
|
||||
// If it can't give a definitive answer, it returns false and an error for which
|
||||
// errors.Is(err, ErrNoStaticIPInfo) is true.
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestBroadcastFromIPNet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckPort(t *testing.T) {
|
||||
laddr := netip.AddrPortFrom(IPv4Localhost(), 0)
|
||||
laddr := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
|
||||
|
||||
t.Run("tcp_bound", func(t *testing.T) {
|
||||
l, err := net.Listen("tcp", laddr.String())
|
||||
|
||||
@@ -168,11 +168,11 @@ func IsOpenWrt() (ok bool) {
|
||||
return isOpenWrt()
|
||||
}
|
||||
|
||||
// RootDirFS returns the fs.FS rooted at the operating system's root.
|
||||
// RootDirFS returns the [fs.FS] rooted at the operating system's root. On
|
||||
// Windows it returns the fs.FS rooted at the volume of the system directory
|
||||
// (usually, C:).
|
||||
func RootDirFS() (fsys fs.FS) {
|
||||
// Use empty string since os.DirFS implicitly prepends a slash to it. This
|
||||
// behavior is undocumented but it currently works.
|
||||
return os.DirFS("")
|
||||
return rootDirFS()
|
||||
}
|
||||
|
||||
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
|
||||
|
||||
@@ -3,12 +3,17 @@
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func rootDirFS() (fsys fs.FS) {
|
||||
return os.DirFS("/")
|
||||
}
|
||||
|
||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGHUP)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,29 @@
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"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) {
|
||||
return Unsupported("setrlimit")
|
||||
}
|
||||
|
||||
@@ -137,14 +137,14 @@ func (c *V4ServerConf) Validate() (err error) {
|
||||
|
||||
gatewayIP, err := ensureV4(c.GatewayIP, "address")
|
||||
if err != nil {
|
||||
// Don't wrap an errors since it's informative enough as is and there is
|
||||
// Don't wrap the error since it's informative enough as is and there is
|
||||
// an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
|
||||
subnetMask, err := ensureV4(c.SubnetMask, "subnet mask")
|
||||
if err != nil {
|
||||
// Don't wrap an errors since it's informative enough as is and there is
|
||||
// Don't wrap the error since it's informative enough as is and there is
|
||||
// an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
@@ -155,20 +155,21 @@ func (c *V4ServerConf) Validate() (err error) {
|
||||
|
||||
rangeStart, err := ensureV4(c.RangeStart, "address")
|
||||
if err != nil {
|
||||
// Don't wrap an errors since it's informative enough as is and there is
|
||||
// Don't wrap the error since it's informative enough as is and there is
|
||||
// an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
|
||||
rangeEnd, err := ensureV4(c.RangeEnd, "address")
|
||||
if err != nil {
|
||||
// Don't wrap an errors since it's informative enough as is and there is
|
||||
// Don't wrap the error since it's informative enough as is and there is
|
||||
// an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
|
||||
c.ipRange, err = newIPRange(rangeStart.AsSlice(), rangeEnd.AsSlice())
|
||||
if err != nil {
|
||||
// Don't wrap an errors since it's informative enough as is and there is
|
||||
// Don't wrap the error since it's informative enough as is and there is
|
||||
// an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -219,8 +219,6 @@ var _ Interface = (*server)(nil)
|
||||
|
||||
// Create initializes and returns the DHCP server handling both address
|
||||
// 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) {
|
||||
s = &server{
|
||||
conf: &ServerConfig{
|
||||
@@ -237,6 +235,8 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Don't register handlers, see TODO on
|
||||
// [aghhttp.RegisterFunc].
|
||||
s.registerHandlers()
|
||||
|
||||
v4conf := conf.Conf4
|
||||
@@ -250,7 +250,7 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||
return nil, fmt.Errorf("creating dhcpv4 srv: %w", err)
|
||||
}
|
||||
|
||||
log.Error("creating dhcpv4 srv: %s", err)
|
||||
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
|
||||
}
|
||||
|
||||
v6conf := conf.Conf6
|
||||
|
||||
@@ -23,16 +23,6 @@ func ValidateClientID(id string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasLabelSuffix returns true if s ends with suffix preceded by a dot. It's
|
||||
// a helper function to prevent unnecessary allocations in code like:
|
||||
//
|
||||
// if strings.HasSuffix(s, "." + suffix) { /* … */ }
|
||||
//
|
||||
// s must be longer than suffix.
|
||||
func hasLabelSuffix(s, suffix string) (ok bool) {
|
||||
return strings.HasSuffix(s, suffix) && s[len(s)-len(suffix)-1] == '.'
|
||||
}
|
||||
|
||||
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
|
||||
// is the server name of the host. cliSrvName is the server name as sent by the
|
||||
// client. When strict is true, and client and host server name don't match,
|
||||
@@ -46,7 +36,7 @@ func clientIDFromClientServerName(
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !hasLabelSuffix(cliSrvName, hostSrvName) {
|
||||
if !netutil.IsImmediateSubdomain(cliSrvName, hostSrvName) {
|
||||
if !strict {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ type RDNSExchanger interface {
|
||||
// Exchange tries to resolve the ip in a suitable way, e.g. either as
|
||||
// local or as external.
|
||||
Exchange(ip net.IP) (host string, err error)
|
||||
|
||||
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to
|
||||
// resolve PTR requests for locally-served addresses.
|
||||
ResolvesPrivatePTR() (ok bool)
|
||||
@@ -261,6 +262,9 @@ const (
|
||||
rDNSNotPTRErr errors.Error = "the response is not a ptr"
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ RDNSExchanger = (*Server)(nil)
|
||||
|
||||
// Exchange implements the RDNSExchanger interface for *Server.
|
||||
func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||
s.serverLock.RLock()
|
||||
@@ -675,21 +679,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// IsBlockedClient returns true if the client is blocked by the current access
|
||||
// settings.
|
||||
func (s *Server) IsBlockedClient(ip net.IP, clientID string) (blocked bool, rule string) {
|
||||
func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
blockedByIP := false
|
||||
if ip != nil {
|
||||
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
|
||||
ipAddr, err := netutil.IPToAddrNoMapped(ip)
|
||||
if err != nil {
|
||||
log.Error("dnsforward: bad client ip %v: %s", ip, err)
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
blockedByIP, rule = s.access.isBlockedIP(ipAddr)
|
||||
if ip != (netip.Addr{}) {
|
||||
blockedByIP, rule = s.access.isBlockedIP(ip)
|
||||
}
|
||||
|
||||
allowlistMode := s.access.allowlistMode()
|
||||
|
||||
@@ -19,13 +19,13 @@ func (s *Server) beforeRequestHandler(
|
||||
_ *proxy.Proxy,
|
||||
pctx *proxy.DNSContext,
|
||||
) (reply bool, err error) {
|
||||
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
||||
clientID, err := s.clientIDFromDNSContext(pctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting clientid: %w", err)
|
||||
}
|
||||
|
||||
blocked, _ := s.IsBlockedClient(ip, clientID)
|
||||
addrPort := netutil.NetAddrToAddrPort(pctx.Addr)
|
||||
blocked, _ := s.IsBlockedClient(addrPort.Addr(), clientID)
|
||||
if blocked {
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package dnsforward
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -565,6 +566,11 @@ type domainSpecificTestError struct {
|
||||
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
|
||||
// healthCheck for actually exchange messages. It uses bootstrap to resolve the
|
||||
// upstream's address.
|
||||
@@ -631,44 +637,54 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
result := map[string]string{}
|
||||
bootstraps := req.BootstrapDNS
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
result[host] = "OK"
|
||||
type upsCheckResult = struct {
|
||||
res string
|
||||
host string
|
||||
}
|
||||
|
||||
for _, host := range req.PrivateUpstreams {
|
||||
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])
|
||||
}
|
||||
upsNum := len(req.Upstreams) + len(req.PrivateUpstreams)
|
||||
resCh := make(chan upsCheckResult, upsNum)
|
||||
|
||||
continue
|
||||
checkUps := func(ups string, healthCheck healthCheckFunc) {
|
||||
res := upsCheckResult{
|
||||
host: ups,
|
||||
}
|
||||
defer func() { resCh <- res }()
|
||||
|
||||
result[host] = "OK"
|
||||
checkErr := checkDNS(ups, bootstraps, timeout, healthCheck)
|
||||
if checkErr != nil {
|
||||
res.res = checkErr.Error()
|
||||
} else {
|
||||
res.res = "OK"
|
||||
}
|
||||
}
|
||||
|
||||
for _, ups := range req.Upstreams {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Control flow:
|
||||
@@ -703,6 +719,8 @@ func (s *Server) registerHandlers() {
|
||||
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/cache_clear", s.handleCacheClear)
|
||||
|
||||
// Register both versions, with and without the trailing slash, to
|
||||
// prevent a 301 Moved Permanently redirect when clients request the
|
||||
// path without the trailing slash. Those redirects break some clients.
|
||||
|
||||
@@ -7,16 +7,20 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -392,3 +396,141 @@ 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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -40,7 +40,7 @@ func serveFiltersLocally(t *testing.T, fltContent []byte) (ipp netip.AddrPort) {
|
||||
addr := l.Addr()
|
||||
require.IsType(t, new(net.TCPAddr), addr)
|
||||
|
||||
return netip.AddrPortFrom(aghnet.IPv4Localhost(), uint16(addr.(*net.TCPAddr).Port))
|
||||
return netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(addr.(*net.TCPAddr).Port))
|
||||
}
|
||||
|
||||
func TestFilters(t *testing.T) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
// The IDs of built-in filter lists.
|
||||
//
|
||||
// Keep in sync with client/src/helpers/constants.js.
|
||||
// TODO(d.kolyshev): Add RewritesListID and don't forget to keep in sync.
|
||||
const (
|
||||
CustomListID = -iota
|
||||
SysHostsListID
|
||||
|
||||
73
internal/filtering/rewrite/item.go
Normal file
73
internal/filtering/rewrite/item.go
Normal file
@@ -0,0 +1,73 @@
|
||||
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
|
||||
}
|
||||
124
internal/filtering/rewrite/item_internal_test.go
Normal file
124
internal/filtering/rewrite/item_internal_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
241
internal/filtering/rewrite/storage.go
Normal file
241
internal/filtering/rewrite/storage.go
Normal file
@@ -0,0 +1,241 @@
|
||||
// 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, "|*.")
|
||||
}
|
||||
458
internal/filtering/rewrite/storage_test.go
Normal file
458
internal/filtering/rewrite/storage_test.go
Normal file
@@ -0,0 +1,458 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
93
internal/filtering/rewritehttp.go
Normal file
93
internal/filtering/rewritehttp.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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()
|
||||
}
|
||||
@@ -3,16 +3,12 @@
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -221,86 +217,3 @@ func max(a, b int) int {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ var blockedServices = []blockedService{{
|
||||
"||amazon.com.au^",
|
||||
"||amazon.com.br^",
|
||||
"||amazon.com.mx^",
|
||||
"||amazon.com.tr^",
|
||||
"||amazon.com^",
|
||||
"||amazon.de^",
|
||||
"||amazon.es^",
|
||||
@@ -73,19 +74,29 @@ var blockedServices = []blockedService{{
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M38 10.813l-.906 3.78-1.907-3.405v1.718c2.899 2.301 4.926 5.79 5.126 9.688.699-.2 1.3-.188 2-.188 1.374 0 2.667.297 3.812.875l-1.031-.593 3.812-.875-3.812-.907L48.5 19h-3.813l2.813-2.688-3.688 1.094 2-3.312-3.312 2 1.094-3.688-2.688 2.781.094-3.874-2 3.28zM27 11c-5 0-9.414 2.992-11.313 7.594-.699-.399-1.687-.688-2.687-.688-3.2 0-5.906 2.606-5.906 5.907v.5c-3.899.3-7.094 3.68-7.094 7.78 0 .802.113 1.52.313 2.22.101.398.5.687 1 .687h47c.398 0 .675-.195.874-.594.5-1.101.813-2.207.813-3.406 0-4.2-3.488-7.594-7.688-7.594-.8 0-1.511.082-2.312.282l4.906 6.625-5.5-4.5L22 29.593l15.094-4.905L28.5 21.5l10.688 1.813v-.125C39.188 16.488 33.699 11 27 11zm19.781 12.656c.434.274.844.586 1.219.938h.5z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||1.1.1.1^",
|
||||
"||argotunnel.com^",
|
||||
"||cloudflare-dns.com^",
|
||||
"||cloudflare-ipfs.com^",
|
||||
"||cloudflare-quic.com^",
|
||||
"||cloudflare.cn^",
|
||||
"||cloudflare.com^",
|
||||
"||cloudflare.net^",
|
||||
"||cloudflareaccess.com^",
|
||||
"||cloudflareapps.com^",
|
||||
"||cloudflarebolt.com^",
|
||||
"||cloudflareclient.com^",
|
||||
"||cloudflareinsights.com^",
|
||||
"||cloudflareresolve.com^",
|
||||
"||cloudflarestatus.com^",
|
||||
"||cloudflarestream.com^",
|
||||
"||cloudflarewarp.com^",
|
||||
"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
|
||||
"||one.one^",
|
||||
"||pages.dev^",
|
||||
"||trycloudflare.com^",
|
||||
"||videodelivery.net^",
|
||||
"||warp.plus^",
|
||||
"||workers.dev^",
|
||||
},
|
||||
}, {
|
||||
ID: "dailymotion",
|
||||
@@ -111,6 +122,7 @@ var blockedServices = []blockedService{{
|
||||
Rules: []string{
|
||||
"||discord.com^",
|
||||
"||discord.gg^",
|
||||
"||discord.gift",
|
||||
"||discord.media^",
|
||||
"||discordapp.com^",
|
||||
"||discordapp.net^",
|
||||
@@ -122,8 +134,20 @@ var blockedServices = []blockedService{{
|
||||
Rules: []string{
|
||||
"||disney-plus.net^",
|
||||
"||disney.playback.edge.bamgrid.com^",
|
||||
"||disneynow.com^",
|
||||
"||disneyplus.com^",
|
||||
"||hotstar.com^",
|
||||
"||media.dssott.com^",
|
||||
"||star.playback.edge.bamgrid.com^",
|
||||
"||starplus.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "douban",
|
||||
Name: "Douban",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M36.54 18.51H13.315v8.012H36.54zM8.58 14.32h32.643v16.39H8.581zM5.146 5.754h39.516v4.193H5.145zm11.051 25.652c1.73 2.416 3.28 5.336 4.647 8.748h8.263c1.637-2.632 3.076-5.552 4.31-8.748l4.75 1.631c-1.241 2.694-2.581 5.07-4.003 7.117h11.615v4.147H4.028v-4.147h12.168c-1.117-2.203-2.568-4.574-4.371-7.117z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||douban.com^",
|
||||
"||doubanio.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "ebay",
|
||||
@@ -176,11 +200,13 @@ var blockedServices = []blockedService{{
|
||||
"||facebook.net^",
|
||||
"||facebookcorewwwi.onion^",
|
||||
"||fb.com^",
|
||||
"||fb.gg^",
|
||||
"||fb.me^",
|
||||
"||fb.watch^",
|
||||
"||fbcdn.com^",
|
||||
"||fbcdn.net^",
|
||||
"||fbsbx.com^",
|
||||
"||fbwat.ch^",
|
||||
"||messenger.com^",
|
||||
},
|
||||
}, {
|
||||
@@ -220,6 +246,111 @@ var blockedServices = []blockedService{{
|
||||
Rules: []string{
|
||||
"||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",
|
||||
Name: "Minecraft",
|
||||
@@ -269,8 +400,9 @@ var blockedServices = []blockedService{{
|
||||
Name: "QQ",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 32 32\"><g fill=\"none\" fillRule=\"evenodd\"><path d=\"M0 0h32v32H0z\" /><g fill=\"currentColor\" fillRule=\"nonzero\"><path d=\"M11.25 32C8.342 32 6 30.74 6 29.242c0-1.497 2.342-2.757 5.25-2.757s5.25 1.26 5.25 2.757S14.158 32 11.25 32zM27 29.242c0-1.497-2.342-2.757-5.25-2.757s-5.25 1.26-5.25 2.757S18.842 32 21.75 32 27 30.74 27 29.242zM14.885 7.182c0 .63-.323 1.182-.808 1.182-.485 0-.808-.552-.808-1.182 0-.63.323-1.182.808-1.182.485 0 .808.552.808 1.182zM18.923 6c-.485 0-.808.552-.808 1.182 0 .63.323-.394.808-.394.485 0 .808 1.024.808.394S19.408 6 18.923 6z\" /><path d=\"M6.653 12.638s4.685 2.465 9.926 2.465c5.242 0 9.927-2.465 9.927-2.465.112-.09.217-.161.316-.212-.002-1.088-.078-2.026-.078-2.808C26.744 4.292 22.138 0 16.5 0S6.176 4.292 6.176 9.618v2.78c.146.042.3.113.477.24zm12.626-8.664c1.112 0 1.986 1.272 1.986 2.782s-.874 2.782-1.986 2.782c-1.111 0-1.985-1.271-1.985-2.782 0-1.51.874-2.782 1.985-2.782zm-5.558 0c1.111 0 1.985 1.272 1.985 2.782s-.874 2.782-1.985 2.782c-1.112 0-1.986-1.271-1.986-2.782 0-1.51.874-2.782 1.986-2.782zm2.779 6.624c2.912 0 5.294.464 5.294.994s-2.382 1.656-5.294 1.656c-2.912 0-5.294-1.126-5.294-1.656s2.382-.994 5.294-.994zm11.374 5.182c-.058.038-.108.076-.177.117-.159.08-5.241 3.18-11.038 3.18-1.43 0-2.7-.239-3.97-.477-.239 1.67-.239 3.259-.239 3.974 0 1.272-1.032 1.193-2.303 1.272-1.27 0-2.223.16-2.303-1.033 0-.16-.08-2.782.397-5.564-1.588-.716-2.62-1.272-2.7-1.352a3.293 3.293 0 01-.335-.216C4.012 17.55 3 19.598 3 21.223c0 3.815 1.112 3.418 1.112 3.418.476 0 1.27-.795 1.985-1.67C7.765 27.662 11.735 31 16.5 31c4.765 0 8.735-3.338 10.403-8.028.715.874 1.509 1.669 1.985 1.669 0 0 1.112.397 1.112-3.418 0-1.588-.968-3.631-2.126-5.443z\" /></g></g></svg>"),
|
||||
Rules: []string{
|
||||
"^(?!weixin|wx)([^.]+\\.)?qq\\.com$",
|
||||
"||qqzaixian.com^",
|
||||
"||qq-video.cdn-go.cn^",
|
||||
"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
|
||||
"||url.cn^",
|
||||
},
|
||||
}, {
|
||||
ID: "reddit",
|
||||
@@ -297,8 +429,11 @@ var blockedServices = []blockedService{{
|
||||
Name: "Skype",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 26 26\"><path d=\"M23.363 14.387c.153-.739.23-1.5.23-2.266C23.594 5.883 18.45.805 12.122.805c-.594 0-1.191.047-1.781.136A6.891 6.891 0 0 0 6.852 0C3.074 0 0 3.035 0 6.762c0 1.144.293 2.27.852 3.265-.133.688-.2 1.391-.2 2.094 0 6.238 5.149 11.316 11.47 11.316.648 0 1.3-.054 1.94-.164.95.477 2.012.727 3.086.727C20.926 24 24 20.969 24 17.238c0-1.004-.215-1.96-.637-2.851zM17.758 17.3c-.508.707-1.258 1.27-2.23 1.668-.966.394-2.122.593-3.434.593-1.578 0-2.903-.273-3.934-.812a5.074 5.074 0 0 1-1.808-1.582c-.47-.664-.707-1.324-.707-1.961 0-.395.156-.738.457-1.023.304-.278.687-.418 1.148-.418.379 0 .703.109.969.332.254.21.469.523.644.93.192.437.407.808.633 1.1.211.282.524.52.918.704.399.188.938.281 1.598.281.91 0 1.652-.191 2.215-.57.546-.367.812-.813.812-1.352 0-.43-.14-.765-.422-1.027-.3-.277-.699-.492-1.176-.637-.5-.152-1.18-.32-2.015-.496-1.14-.238-2.11-.523-2.88-.847-.788-.332-1.425-.79-1.89-1.364-.472-.582-.71-1.312-.71-2.172 0-.816.253-1.554.75-2.191.488-.633 1.206-1.125 2.132-1.46.91-.333 1.996-.5 3.223-.5.98 0 1.844.108 2.566.331.723.223 1.336.524 1.813.89.484.376.843.774 1.07 1.188.227.418.344.832.344 1.235 0 .386-.153.738-.453 1.046-.297.31-.68.465-1.125.465-.41 0-.727-.097-.95-.289-.207-.18-.418-.46-.656-.863-.273-.516-.605-.918-.984-1.203-.371-.277-.989-.418-1.836-.418-.79 0-1.43.156-1.902.465-.461.293-.684.633-.684 1.039 0 .246.07.449.219.629.156.187.379.351.656.488.289.145.586.258.883.34.308.082.82.207 1.523.367.887.191 1.707.398 2.43.625.73.234 1.363.516 1.879.848.527.34.941.773 1.238 1.293.297.52.445 1.16.445 1.91a4.07 4.07 0 0 1-.77 2.418zm0 0\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||edge-skype-com.s-0001.s-msedge.net^",
|
||||
"||skype-edf.akadns.net^",
|
||||
"||skype.com^",
|
||||
"||skypeassets.com^",
|
||||
"||skypedata.akadns.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "snapchat",
|
||||
@@ -359,10 +494,14 @@ var blockedServices = []blockedService{{
|
||||
"||bdurl.com^",
|
||||
"||bytecdn.cn^",
|
||||
"||bytedance.map.fastly.net^",
|
||||
"||bytedapm.com^",
|
||||
"||byteimg.com^",
|
||||
"||byteoversea.com^",
|
||||
"||douyin.com^",
|
||||
"||douyincdn.com^",
|
||||
"||ixigua.com^",
|
||||
"||douyinpic.com^",
|
||||
"||douyinstatic.com^",
|
||||
"||douyinvod.com^",
|
||||
"||ixigua.com^",
|
||||
"||ixiguavideo.com^",
|
||||
"||muscdn.com^",
|
||||
@@ -375,6 +514,7 @@ var blockedServices = []blockedService{{
|
||||
"||toutiao.com^",
|
||||
"||toutiaocloud.com^",
|
||||
"||toutiaocloud.net^",
|
||||
"||toutiaovod.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "tinder",
|
||||
@@ -437,7 +577,9 @@ var blockedServices = []blockedService{{
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 19 6 C 9.625 6 2 12.503906 2 20.5 C 2 24.769531 4.058594 28.609375 7.816406 31.390625 L 5.179688 39.304688 L 13.425781 34.199219 C 15.714844 34.917969 18.507813 35.171875 21.203125 34.875 C 23.390625 39.109375 28.332031 42 34 42 C 35.722656 42 37.316406 41.675781 38.796875 41.234375 L 45.644531 45.066406 L 43.734375 38.515625 C 46.3125 36.375 48 33.394531 48 30 C 48 23.789063 42.597656 18.835938 35.75 18.105469 C 34.40625 11.152344 27.367188 6 19 6 Z M 13 14 C 14.101563 14 15 14.898438 15 16 C 15 17.101563 14.101563 18 13 18 C 11.898438 18 11 17.101563 11 16 C 11 14.898438 11.898438 14 13 14 Z M 25 14 C 26.101563 14 27 14.898438 27 16 C 27 17.101563 26.101563 18 25 18 C 23.898438 18 23 17.101563 23 16 C 23 14.898438 23.898438 14 25 14 Z M 34 20 C 40.746094 20 46 24.535156 46 30 C 46 32.957031 44.492188 35.550781 42.003906 37.394531 L 41.445313 37.8125 L 42.355469 40.933594 L 39.105469 39.109375 L 38.683594 39.25 C 37.285156 39.71875 35.6875 40 34 40 C 27.253906 40 22 35.464844 22 30 C 22 24.535156 27.253906 20 34 20 Z M 29.5 26 C 28.699219 26 28 26.699219 28 27.5 C 28 28.300781 28.699219 29 29.5 29 C 30.300781 29 31 28.300781 31 27.5 C 31 26.699219 30.300781 26 29.5 26 Z M 38.5 26 C 37.699219 26 37 26.699219 37 27.5 C 37 28.300781 37.699219 29 38.5 29 C 39.300781 29 40 28.300781 40 27.5 C 40 26.699219 39.300781 26 38.5 26 Z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||wechat.com^",
|
||||
"||weixin.qq.com.cn^",
|
||||
"||weixin.qq.com^",
|
||||
"||weixinbridge.com^",
|
||||
"||wx.qq.com^",
|
||||
},
|
||||
}, {
|
||||
@@ -445,7 +587,9 @@ var blockedServices = []blockedService{{
|
||||
Name: "Weibo",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 35 6 C 34.222656 6 33.472656 6.078125 32.75 6.207031 C 32.207031 6.300781 31.84375 6.820313 31.9375 7.363281 C 32.03125 7.910156 32.550781 8.273438 33.09375 8.179688 C 33.726563 8.066406 34.359375 8 35 8 C 41.085938 8 46 12.914063 46 19 C 46 20.316406 45.757813 21.574219 45.328125 22.753906 C 45.195313 23.09375 45.253906 23.476563 45.484375 23.757813 C 45.71875 24.039063 46.082031 24.171875 46.441406 24.105469 C 46.800781 24.039063 47.09375 23.78125 47.207031 23.4375 C 47.710938 22.054688 48 20.566406 48 19 C 48 11.832031 42.167969 6 35 6 Z M 35 12 C 34.574219 12 34.171875 12.042969 33.789063 12.109375 C 33.246094 12.207031 32.878906 12.722656 32.976563 13.269531 C 33.070313 13.8125 33.589844 14.175781 34.132813 14.082031 C 34.425781 14.03125 34.714844 14 35 14 C 37.773438 14 40 16.226563 40 19 C 40 19.597656 39.890625 20.167969 39.691406 20.707031 C 39.503906 21.226563 39.773438 21.800781 40.292969 21.988281 C 40.8125 22.175781 41.386719 21.910156 41.574219 21.390625 C 41.84375 20.648438 42 19.84375 42 19 C 42 15.144531 38.855469 12 35 12 Z M 21.175781 12.40625 C 17.964844 12.34375 13.121094 14.878906 8.804688 19.113281 C 4.511719 23.40625 2 27.90625 2 31.78125 C 2 39.3125 11.628906 43.8125 21.152344 43.8125 C 33.5 43.8125 41.765625 36.699219 41.765625 31.046875 C 41.765625 27.59375 38.835938 25.707031 36.21875 24.871094 C 35.59375 24.660156 35.175781 24.558594 35.488281 23.71875 C 35.695313 23.21875 36 22.265625 36 21 C 36 19.5625 35 18.316406 33 18.09375 C 32.007813 17.984375 28 18 25.339844 19.113281 C 25.339844 19.113281 23.871094 19.746094 24.289063 18.59375 C 25.023438 16.292969 24.917969 14.40625 23.765625 13.359375 C 23.140625 12.730469 22.25 12.425781 21.175781 12.40625 Z M 20.3125 23.933594 C 28.117188 23.933594 34.441406 27.914063 34.441406 32.828125 C 34.441406 37.738281 28.117188 41.71875 20.3125 41.71875 C 12.511719 41.71875 6.1875 37.738281 6.1875 32.828125 C 6.1875 27.914063 12.511719 23.933594 20.3125 23.933594 Z M 19.265625 26.023438 C 16.246094 26.046875 13.3125 27.699219 12.039063 30.246094 C 10.46875 33.484375 11.933594 37.042969 15.699219 38.191406 C 19.464844 39.445313 23.960938 37.5625 25.53125 34.113281 C 27.097656 30.769531 25.113281 27.214844 21.347656 26.277344 C 20.660156 26.097656 19.960938 26.019531 19.265625 26.023438 Z M 20.824219 30.25 C 21.402344 30.25 21.871094 30.714844 21.871094 31.292969 C 21.871094 31.871094 21.402344 32.339844 20.824219 32.339844 C 20.246094 32.339844 19.777344 31.871094 19.777344 31.292969 C 19.777344 30.714844 20.246094 30.25 20.824219 30.25 Z M 16.417969 31.292969 C 16.746094 31.296875 17.074219 31.347656 17.382813 31.453125 C 18.722656 31.878906 19.132813 33.148438 18.308594 34.207031 C 17.589844 35.265625 15.945313 35.792969 14.707031 35.265625 C 13.476563 34.738281 13.167969 33.464844 13.886719 32.515625 C 14.425781 31.71875 15.429688 31.28125 16.417969 31.292969 Z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||weibo.cn^",
|
||||
"||weibo.com^",
|
||||
"||weibocdn.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "whatsapp",
|
||||
@@ -461,11 +605,21 @@ var blockedServices = []blockedService{{
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M19.695 4.04S15.348 3.2 12 3.2s-7.695.84-7.695.84L1.602 7.2v9.6l2.703 3.16s4.347.84 7.695.84 7.695-.84 7.695-.84l2.703-3.16V12 7.2zM9.602 15.68V8.32L16 12zm0 0\" /><path d=\"M19.2 4a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.43 3.198-3.199C22.398 5.434 20.968 4 19.2 4zm0 9.602a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.434 3.198-3.2 0-1.769-1.43-3.198-3.199-3.198zM1.601 7.199c0 1.77 1.43 3.2 3.199 3.2 1.765 0 2.398-1.43 2.398-3.2C7.2 5.434 6.566 4 4.801 4 3.03 4 1.6 5.434 1.6 7.2zM4.8 13.602c-1.77 0-3.2 1.43-3.2 3.199A3.198 3.198 0 1 0 8 16.8c0-1.77-1.434-3.2-3.2-3.2zm0 0\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||googlevideo.com^",
|
||||
"||wide-youtube.l.google.com^",
|
||||
"||youtu.be^",
|
||||
"||youtube",
|
||||
"||youtube-nocookie.com^",
|
||||
"||youtube.com^",
|
||||
"||youtubei.googleapis.com^",
|
||||
"||youtubekids.com^",
|
||||
"||ytimg.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "zhihu",
|
||||
Name: "Zhihu",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 30 30\"><path d=\"M14.46 14.191H9.982c0-.471.033-.954.039-1.458v-5.5h5.106V5.935a1.352 1.352 0 00-.404-.957 1.378 1.378 0 00-.968-.396H5.783c.028-.088.056-.177.084-.255.274-.82 1.153-3.326 1.153-3.326a4.262 4.262 0 00-2.413.698c-.57.4-.912.682-1.371 1.946-.532 1.453-.997 2.856-1.31 3.693C1.444 8.674.28 11.025.28 11.025a5.85 5.85 0 002.52-.61c1.119-.593 1.679-1.502 2.054-2.883l.09-.3h2.334v5.5c0 .5-.045.982-.073 1.46h-4.12c-.71 0-1.39.278-1.893.775a2.638 2.638 0 00-.783 1.874h6.527a17.717 17.717 0 01-.778 3.649 16.796 16.796 0 01-3.012 5.273A33.104 33.104 0 010 28.74s3.13 1.175 5.425-.954c1.388-1.292 2.631-3.814 3.23-5.727a28.09 28.09 0 001.12-5.229h5.967v-1.37a1.254 1.254 0 00-.373-.899 1.279 1.279 0 00-.909-.37zm-3.19 5.484l-2.312 1.491 5.038 7.458a6.905 6.905 0 00.672-2.218 3.15 3.15 0 00-.28-2.168l-3.118-4.563zM29.05 4.582H16.733V25.94h3.018l.403 2.572 4.081-2.572h4.815V4.582zm-5.207 18.69l-2.396 1.509-.235-1.508h-1.724V7.233h6.78v16.04h-2.425z\" /></svg>"),
|
||||
Rules: []string{
|
||||
"||zhihu.com^",
|
||||
"||zhimg.com^",
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -129,7 +129,7 @@ type RuntimeClientWHOISInfo struct {
|
||||
|
||||
type clientsContainer struct {
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for
|
||||
// different types (string, net.IP, and so on).
|
||||
// different types (string, netip.Addr, and so on).
|
||||
list map[string]*Client // name -> client
|
||||
idIndex map[string]*Client // ID -> client
|
||||
|
||||
@@ -333,7 +333,7 @@ func (clients *clientsContainer) onDHCPLeaseChanged(flags int) {
|
||||
}
|
||||
|
||||
// exists checks if client with this IP address already exists.
|
||||
func (clients *clientsContainer) exists(ip net.IP, source clientSource) (ok bool) {
|
||||
func (clients *clientsContainer) exists(ip netip.Addr, source clientSource) (ok bool) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
@@ -342,7 +342,7 @@ func (clients *clientsContainer) exists(ip net.IP, source clientSource) (ok bool
|
||||
return true
|
||||
}
|
||||
|
||||
rc, ok := clients.findRuntimeClientLocked(ip)
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -371,7 +371,8 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
|
||||
var artClient *querylog.Client
|
||||
var art bool
|
||||
for _, id := range ids {
|
||||
c, art = clients.clientOrArtificial(net.ParseIP(id), id)
|
||||
ip, _ := netip.ParseAddr(id)
|
||||
c, art = clients.clientOrArtificial(ip, id)
|
||||
if art {
|
||||
artClient = c
|
||||
|
||||
@@ -389,7 +390,7 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
|
||||
// records about this client besides maybe whether or not it is blocked. c is
|
||||
// never nil.
|
||||
func (clients *clientsContainer) clientOrArtificial(
|
||||
ip net.IP,
|
||||
ip netip.Addr,
|
||||
id string,
|
||||
) (c *querylog.Client, art bool) {
|
||||
defer func() {
|
||||
@@ -406,13 +407,6 @@ func (clients *clientsContainer) clientOrArtificial(
|
||||
}, false
|
||||
}
|
||||
|
||||
if ip == nil {
|
||||
// Technically should never happen, but still.
|
||||
return &querylog.Client{
|
||||
Name: "",
|
||||
}, true
|
||||
}
|
||||
|
||||
var rc *RuntimeClient
|
||||
rc, ok = clients.findRuntimeClient(ip)
|
||||
if ok {
|
||||
@@ -492,19 +486,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
return c, true
|
||||
}
|
||||
|
||||
ip := net.ParseIP(id)
|
||||
if ip == nil {
|
||||
ip, err := netip.ParseAddr(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
_, ipnet, err := net.ParseCIDR(id)
|
||||
var n netip.Prefix
|
||||
n, err = netip.ParsePrefix(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ipnet.Contains(ip) {
|
||||
if n.Contains(ip) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
@@ -514,19 +509,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
macFound := clients.dhcpServer.FindMACbyIP(ip)
|
||||
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
|
||||
if macFound == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
hwAddr, err := net.ParseMAC(id)
|
||||
var mac net.HardwareAddr
|
||||
mac, err = net.ParseMAC(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(hwAddr, macFound) {
|
||||
if bytes.Equal(mac, macFound) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
@@ -535,32 +531,18 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// findRuntimeClientLocked finds a runtime client by their IP address. For
|
||||
// internal use only.
|
||||
func (clients *clientsContainer) findRuntimeClientLocked(ip net.IP) (rc *RuntimeClient, ok bool) {
|
||||
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
|
||||
ipAddr, err := netutil.IPToAddrNoMapped(ip)
|
||||
if err != nil {
|
||||
log.Error("clients: bad client ip %v: %s", ip, err)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rc, ok = clients.ipToRC[ipAddr]
|
||||
|
||||
return rc, ok
|
||||
}
|
||||
|
||||
// findRuntimeClient finds a runtime client by their IP.
|
||||
func (clients *clientsContainer) findRuntimeClient(ip net.IP) (rc *RuntimeClient, ok bool) {
|
||||
if ip == nil {
|
||||
func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
|
||||
if ip == (netip.Addr{}) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
return clients.findRuntimeClientLocked(ip)
|
||||
rc, ok = clients.ipToRC[ip]
|
||||
|
||||
return rc, ok
|
||||
}
|
||||
|
||||
// check validates the client.
|
||||
@@ -578,14 +560,16 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||
|
||||
for i, id := range c.IDs {
|
||||
// Normalize structured data.
|
||||
var ip net.IP
|
||||
var ipnet *net.IPNet
|
||||
var mac net.HardwareAddr
|
||||
if ip = net.ParseIP(id); ip != nil {
|
||||
var (
|
||||
ip netip.Addr
|
||||
n netip.Prefix
|
||||
mac net.HardwareAddr
|
||||
)
|
||||
|
||||
if ip, err = netip.ParseAddr(id); err == nil {
|
||||
c.IDs[i] = ip.String()
|
||||
} else if ip, ipnet, err = net.ParseCIDR(id); err == nil {
|
||||
ipnet.IP = ip
|
||||
c.IDs[i] = ipnet.String()
|
||||
} else if n, err = netip.ParsePrefix(id); err == nil {
|
||||
c.IDs[i] = n.String()
|
||||
} else if mac, err = net.ParseMAC(id); err == nil {
|
||||
c.IDs[i] = mac.String()
|
||||
} else if err = dnsforward.ValidateClientID(id); err == nil {
|
||||
@@ -750,7 +734,7 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||
}
|
||||
|
||||
// setWHOISInfo sets the WHOIS information for a client.
|
||||
func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISInfo) {
|
||||
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
@@ -760,7 +744,7 @@ func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
|
||||
return
|
||||
}
|
||||
|
||||
rc, ok := clients.findRuntimeClientLocked(ip)
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if ok {
|
||||
rc.WHOISInfo = wi
|
||||
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
|
||||
@@ -776,32 +760,22 @@ func (clients *clientsContainer) setWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
|
||||
|
||||
rc.WHOISInfo = wi
|
||||
|
||||
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
|
||||
ipAddr, err := netutil.IPToAddrNoMapped(ip)
|
||||
if err != nil {
|
||||
log.Error("clients: bad client ip %v: %s", ip, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
clients.ipToRC[ipAddr] = rc
|
||||
clients.ipToRC[ip] = rc
|
||||
|
||||
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
|
||||
}
|
||||
|
||||
// AddHost adds a new IP-hostname pairing. The priorities of the sources are
|
||||
// taken into account. ok is true if the pairing was added.
|
||||
func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSource) (ok bool, err error) {
|
||||
func (clients *clientsContainer) AddHost(
|
||||
ip netip.Addr,
|
||||
host string,
|
||||
src clientSource,
|
||||
) (ok bool) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
|
||||
ipAddr, err := netutil.IPToAddrNoMapped(ip)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("adding host: %w", err)
|
||||
}
|
||||
|
||||
return clients.addHostLocked(ipAddr, host, src), nil
|
||||
return clients.addHostLocked(ip, host, src)
|
||||
}
|
||||
|
||||
// addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be
|
||||
|
||||
@@ -22,8 +22,18 @@ func TestClients(t *testing.T) {
|
||||
clients.Init(nil, nil, nil, nil)
|
||||
|
||||
t.Run("add_success", func(t *testing.T) {
|
||||
var (
|
||||
cliNone = "1.2.3.4"
|
||||
cli1 = "1.1.1.1"
|
||||
cli2 = "2.2.2.2"
|
||||
|
||||
cliNoneIP = netip.MustParseAddr(cliNone)
|
||||
cli1IP = netip.MustParseAddr(cli1)
|
||||
cli2IP = netip.MustParseAddr(cli2)
|
||||
)
|
||||
|
||||
c := &Client{
|
||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
||||
IDs: []string{cli1, "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
||||
Name: "client1",
|
||||
}
|
||||
|
||||
@@ -33,7 +43,7 @@ func TestClients(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
|
||||
c = &Client{
|
||||
IDs: []string{"2.2.2.2"},
|
||||
IDs: []string{cli2},
|
||||
Name: "client2",
|
||||
}
|
||||
|
||||
@@ -42,7 +52,7 @@ func TestClients(t *testing.T) {
|
||||
|
||||
assert.True(t, ok)
|
||||
|
||||
c, ok = clients.Find("1.1.1.1")
|
||||
c, ok = clients.Find(cli1)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "client1", c.Name)
|
||||
@@ -52,14 +62,14 @@ func TestClients(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "client1", c.Name)
|
||||
|
||||
c, ok = clients.Find("2.2.2.2")
|
||||
c, ok = clients.Find(cli2)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "client2", c.Name)
|
||||
|
||||
assert.False(t, clients.exists(net.IP{1, 2, 3, 4}, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(net.IP{2, 2, 2, 2}, ClientSourceHostsFile))
|
||||
assert.False(t, clients.exists(cliNoneIP, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(cli1IP, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(cli2IP, ClientSourceHostsFile))
|
||||
})
|
||||
|
||||
t.Run("add_fail_name", func(t *testing.T) {
|
||||
@@ -103,23 +113,31 @@ func TestClients(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("update_success", func(t *testing.T) {
|
||||
var (
|
||||
cliOld = "1.1.1.1"
|
||||
cliNew = "1.1.1.2"
|
||||
|
||||
cliOldIP = netip.MustParseAddr(cliOld)
|
||||
cliNewIP = netip.MustParseAddr(cliNew)
|
||||
)
|
||||
|
||||
err := clients.Update("client1", &Client{
|
||||
IDs: []string{"1.1.1.2"},
|
||||
IDs: []string{cliNew},
|
||||
Name: "client1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, clients.exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile))
|
||||
assert.False(t, clients.exists(cliOldIP, ClientSourceHostsFile))
|
||||
assert.True(t, clients.exists(cliNewIP, ClientSourceHostsFile))
|
||||
|
||||
err = clients.Update("client1", &Client{
|
||||
IDs: []string{"1.1.1.2"},
|
||||
IDs: []string{cliNew},
|
||||
Name: "client1-renamed",
|
||||
UseOwnSettings: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
c, ok := clients.Find("1.1.1.2")
|
||||
c, ok := clients.Find(cliNew)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "client1-renamed", c.Name)
|
||||
@@ -132,14 +150,14 @@ func TestClients(t *testing.T) {
|
||||
|
||||
require.Len(t, c.IDs, 1)
|
||||
|
||||
assert.Equal(t, "1.1.1.2", c.IDs[0])
|
||||
assert.Equal(t, cliNew, c.IDs[0])
|
||||
})
|
||||
|
||||
t.Run("del_success", func(t *testing.T) {
|
||||
ok := clients.Del("client1-renamed")
|
||||
require.True(t, ok)
|
||||
|
||||
assert.False(t, clients.exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile))
|
||||
assert.False(t, clients.exists(netip.MustParseAddr("1.1.1.2"), ClientSourceHostsFile))
|
||||
})
|
||||
|
||||
t.Run("del_fail", func(t *testing.T) {
|
||||
@@ -148,45 +166,33 @@ func TestClients(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("addhost_success", func(t *testing.T) {
|
||||
ip := net.IP{1, 1, 1, 1}
|
||||
|
||||
ok, err := clients.AddHost(ip, "host", ClientSourceARP)
|
||||
require.NoError(t, err)
|
||||
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
ok := clients.AddHost(ip, "host", ClientSourceARP)
|
||||
assert.True(t, ok)
|
||||
|
||||
ok, err = clients.AddHost(ip, "host2", ClientSourceARP)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = clients.AddHost(ip, "host2", ClientSourceARP)
|
||||
assert.True(t, ok)
|
||||
|
||||
ok, err = clients.AddHost(ip, "host3", ClientSourceHostsFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = clients.AddHost(ip, "host3", ClientSourceHostsFile)
|
||||
assert.True(t, ok)
|
||||
|
||||
assert.True(t, clients.exists(ip, ClientSourceHostsFile))
|
||||
})
|
||||
|
||||
t.Run("dhcp_replaces_arp", func(t *testing.T) {
|
||||
ip := net.IP{1, 2, 3, 4}
|
||||
|
||||
ok, err := clients.AddHost(ip, "from_arp", ClientSourceARP)
|
||||
require.NoError(t, err)
|
||||
|
||||
ip := netip.MustParseAddr("1.2.3.4")
|
||||
ok := clients.AddHost(ip, "from_arp", ClientSourceARP)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, clients.exists(ip, ClientSourceARP))
|
||||
|
||||
ok, err = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, clients.exists(ip, ClientSourceDHCP))
|
||||
})
|
||||
|
||||
t.Run("addhost_fail", func(t *testing.T) {
|
||||
ok, err := clients.AddHost(net.IP{1, 1, 1, 1}, "host1", ClientSourceRDNS)
|
||||
require.NoError(t, err)
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
ok := clients.AddHost(ip, "host1", ClientSourceRDNS)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
@@ -203,7 +209,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
|
||||
t.Run("new_client", func(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.255")
|
||||
clients.setWHOISInfo(ip.AsSlice(), whois)
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
require.NotNil(t, rc)
|
||||
|
||||
@@ -212,12 +218,10 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
|
||||
t.Run("existing_auto-client", func(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
ok, err := clients.AddHost(ip.AsSlice(), "host", ClientSourceRDNS)
|
||||
require.NoError(t, err)
|
||||
|
||||
ok := clients.AddHost(ip, "host", ClientSourceRDNS)
|
||||
assert.True(t, ok)
|
||||
|
||||
clients.setWHOISInfo(ip.AsSlice(), whois)
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
require.NotNil(t, rc)
|
||||
|
||||
@@ -234,7 +238,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
clients.setWHOISInfo(ip.AsSlice(), whois)
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
require.Nil(t, rc)
|
||||
|
||||
@@ -249,7 +253,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
clients.Init(nil, nil, nil, nil)
|
||||
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
ip := net.IP{1, 1, 1, 1}
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
|
||||
// Add a client.
|
||||
ok, err := clients.Add(&Client{
|
||||
@@ -260,8 +264,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
|
||||
// Now add an auto-client with the same IP.
|
||||
ok, err = clients.AddHost(ip, "test", ClientSourceRDNS)
|
||||
require.NoError(t, err)
|
||||
ok = clients.AddHost(ip, "test", ClientSourceRDNS)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package home
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
)
|
||||
@@ -47,8 +47,8 @@ type runtimeClientJSON struct {
|
||||
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"`
|
||||
|
||||
Name string `json:"name"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
Source clientSource `json:"source"`
|
||||
IP net.IP `json:"ip"`
|
||||
}
|
||||
|
||||
type clientListJSON struct {
|
||||
@@ -75,7 +75,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
||||
|
||||
Name: rc.Host,
|
||||
Source: rc.Source,
|
||||
IP: ip.AsSlice(),
|
||||
IP: ip,
|
||||
}
|
||||
|
||||
data.RuntimeClients = append(data.RuntimeClients, cj)
|
||||
@@ -218,7 +218,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
||||
break
|
||||
}
|
||||
|
||||
ip := net.ParseIP(idStr)
|
||||
ip, _ := netip.ParseAddr(idStr)
|
||||
c, ok := clients.Find(idStr)
|
||||
var cj *clientJSON
|
||||
if !ok {
|
||||
@@ -240,7 +240,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
||||
// findRuntime looks up the IP in runtime and temporary storages, like
|
||||
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
|
||||
// non-nil.
|
||||
func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj *clientJSON) {
|
||||
func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *clientJSON) {
|
||||
rc, ok := clients.findRuntimeClient(ip)
|
||||
if !ok {
|
||||
// It is still possible that the IP used to be in the runtime clients
|
||||
|
||||
@@ -71,9 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
|
||||
// on, including the addresses on all interfaces in cases of unspecified IPs.
|
||||
func collectDNSAddresses() (addrs []string, err error) {
|
||||
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
|
||||
addr := aghnet.IPv4Localhost()
|
||||
|
||||
addrs = appendDNSAddrs(addrs, addr)
|
||||
addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost())
|
||||
} else {
|
||||
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -150,8 +151,8 @@ func isRunning() bool {
|
||||
}
|
||||
|
||||
func onDNSRequest(pctx *proxy.DNSContext) {
|
||||
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
||||
if ip == nil {
|
||||
ip := netutil.NetAddrToAddrPort(pctx.Addr).Addr()
|
||||
if ip == (netip.Addr{}) {
|
||||
// This would be quite weird if we get here.
|
||||
return
|
||||
}
|
||||
@@ -160,7 +161,8 @@ func onDNSRequest(pctx *proxy.DNSContext) {
|
||||
if srcs.RDNS && !ip.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
|
||||
|
||||
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
|
||||
Context.whois.Begin(ip)
|
||||
}
|
||||
}
|
||||
@@ -193,11 +195,7 @@ func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
|
||||
|
||||
func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||
dnsConf := config.DNS
|
||||
hosts := dnsConf.BindHosts
|
||||
if len(hosts) == 0 {
|
||||
hosts = []netip.Addr{aghnet.IPv4Localhost()}
|
||||
}
|
||||
|
||||
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
|
||||
newConf = dnsforward.ServerConfig{
|
||||
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
|
||||
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
|
||||
@@ -400,15 +398,12 @@ func startDNSServer() error {
|
||||
|
||||
const topClientsNumber = 100 // the number of clients to get
|
||||
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
srcs := config.Clients.Sources
|
||||
if srcs.RDNS && !ip.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
|
||||
|
||||
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
|
||||
Context.whois.Begin(ip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,7 @@ type homeContext struct {
|
||||
|
||||
configFilename string // Config filename (can be overridden via the command line arguments)
|
||||
workDir string // Location of our directory, used to protect against CWD being somewhere else
|
||||
firstRun bool // if set to true, don't run any services except HTTP web interface, and serve only first-run html
|
||||
pidFileName string // PID file name. Empty if no PID file was created.
|
||||
disableUpdate bool // If set, don't check for updates
|
||||
controlLock sync.Mutex
|
||||
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
|
||||
transport *http.Transport
|
||||
@@ -88,6 +86,13 @@ type homeContext struct {
|
||||
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
|
||||
tlsCipherIDs []uint16
|
||||
|
||||
// disableUpdate, if true, tells AdGuard Home to not check for updates.
|
||||
disableUpdate bool
|
||||
|
||||
// firstRun, if true, tells AdGuard Home to only start the web interface
|
||||
// service, and only serve the first-run APIs.
|
||||
firstRun bool
|
||||
|
||||
// runningAsService flag is set to true when options are passed from the service runner
|
||||
runningAsService bool
|
||||
}
|
||||
@@ -462,6 +467,15 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
||||
// See profileSupportsDelta in src/net/http/pprof/pprof.go.
|
||||
mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
|
||||
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
|
||||
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
|
||||
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
|
||||
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
|
||||
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
|
||||
|
||||
go func() {
|
||||
log.Info("pprof: listening on localhost:6060")
|
||||
lerr := http.ListenAndServe("localhost:6060", mux)
|
||||
@@ -500,7 +514,8 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
|
||||
Context.tls, err = newTLSManager(config.TLS)
|
||||
if err != nil {
|
||||
log.Fatalf("initializing tls: %s", err)
|
||||
log.Error("initializing tls: %s", err)
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
Context.web, err = initWeb(opts, clientBuildFS)
|
||||
@@ -528,6 +543,11 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(a.garipov): This could be made much earlier and could be done on
|
||||
// the first run as well, but to achieve this we need to bypass requests
|
||||
// over dnsforward resolver.
|
||||
cmdlineUpdate(opts)
|
||||
|
||||
Context.web.Start()
|
||||
|
||||
// wait indefinitely for other go-routines to complete their job
|
||||
@@ -562,7 +582,7 @@ func checkPermissions() {
|
||||
}
|
||||
|
||||
// We should check if AdGuard Home is able to bind to port 53
|
||||
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(aghnet.IPv4Localhost(), defaultPortDNS))
|
||||
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(netutil.IPv4Localhost(), defaultPortDNS))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
log.Fatal(`Permission check failed.
|
||||
@@ -913,3 +933,37 @@ type jsonError struct {
|
||||
// Message is the error message, an opaque string.
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// cmdlineUpdate updates current application and exits.
|
||||
func cmdlineUpdate(opts options) {
|
||||
if !opts.performUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("starting update")
|
||||
|
||||
if Context.firstRun {
|
||||
log.Info("update not allowed on first run")
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
_, err := Context.updater.VersionInfo(true)
|
||||
if err != nil {
|
||||
vcu := Context.updater.VersionCheckURL()
|
||||
log.Error("getting version info from %s: %s", vcu, err)
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if Context.updater.NewVersion() == "" {
|
||||
log.Info("no updates available")
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = Context.updater.Update()
|
||||
fatalOnError(err)
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ type options struct {
|
||||
// disableUpdate, if set, makes AdGuard Home not check for updates.
|
||||
disableUpdate bool
|
||||
|
||||
// performUpdate, if set, updates AdGuard Home without GUI and exits.
|
||||
performUpdate bool
|
||||
|
||||
// verbose shows if verbose logging is enabled.
|
||||
verbose bool
|
||||
|
||||
@@ -221,6 +224,14 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||
description: "Don't check for updates.",
|
||||
longName: "no-check-update",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: func(o options) (options, error) { o.performUpdate = true; return o, nil },
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) { return "", o.performUpdate },
|
||||
description: "Update application and exit.",
|
||||
longName: "update",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: nil,
|
||||
updateNoValue: nil,
|
||||
|
||||
@@ -103,6 +103,11 @@ func TestParseDisableUpdate(t *testing.T) {
|
||||
assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update")
|
||||
}
|
||||
|
||||
func TestParsePerformUpdate(t *testing.T) {
|
||||
assert.False(t, testParseOK(t).performUpdate, "empty is not perform update")
|
||||
assert.True(t, testParseOK(t, "--update").performUpdate, "--update is perform update")
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Remove after v0.108.0.
|
||||
func TestParseDisableMemoryOptimization(t *testing.T) {
|
||||
o, eff, err := parseCmdOpts("", []string{"--no-mem-optimization"})
|
||||
@@ -169,6 +174,10 @@ func TestOptsToArgs(t *testing.T) {
|
||||
name: "disable_update",
|
||||
args: []string{"--no-check-update"},
|
||||
opts: options{disableUpdate: true},
|
||||
}, {
|
||||
name: "perform_update",
|
||||
args: []string{"--update"},
|
||||
opts: options{performUpdate: true},
|
||||
}, {
|
||||
name: "control_action",
|
||||
args: []string{"-s", "run"},
|
||||
|
||||
@@ -2,7 +2,7 @@ package home
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -21,7 +21,7 @@ type RDNS struct {
|
||||
usePrivate uint32
|
||||
|
||||
// ipCh used to pass client's IP to rDNS workerLoop.
|
||||
ipCh chan net.IP
|
||||
ipCh chan netip.Addr
|
||||
|
||||
// ipCache caches the IP addresses to be resolved by rDNS. The resolved
|
||||
// address stays here while it's inside clients. After leaving clients the
|
||||
@@ -50,7 +50,7 @@ func NewRDNS(
|
||||
EnableLRU: true,
|
||||
MaxCount: defaultRDNSCacheSize,
|
||||
}),
|
||||
ipCh: make(chan net.IP, defaultRDNSIPChSize),
|
||||
ipCh: make(chan netip.Addr, defaultRDNSIPChSize),
|
||||
}
|
||||
if usePrivate {
|
||||
rDNS.usePrivate = 1
|
||||
@@ -80,9 +80,10 @@ func (r *RDNS) ensurePrivateCache() {
|
||||
|
||||
// isCached returns true if ip is already cached and not expired yet. It also
|
||||
// caches it otherwise.
|
||||
func (r *RDNS) isCached(ip net.IP) (ok bool) {
|
||||
func (r *RDNS) isCached(ip netip.Addr) (ok bool) {
|
||||
ipBytes := ip.AsSlice()
|
||||
now := uint64(time.Now().Unix())
|
||||
if expire := r.ipCache.Get(ip); len(expire) != 0 {
|
||||
if expire := r.ipCache.Get(ipBytes); len(expire) != 0 {
|
||||
if binary.BigEndian.Uint64(expire) > now {
|
||||
return true
|
||||
}
|
||||
@@ -91,14 +92,14 @@ func (r *RDNS) isCached(ip net.IP) (ok bool) {
|
||||
// The cache entry either expired or doesn't exist.
|
||||
ttl := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL)
|
||||
r.ipCache.Set(ip, ttl)
|
||||
r.ipCache.Set(ipBytes, ttl)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Begin adds the ip to the resolving queue if it is not cached or already
|
||||
// resolved.
|
||||
func (r *RDNS) Begin(ip net.IP) {
|
||||
func (r *RDNS) Begin(ip netip.Addr) {
|
||||
r.ensurePrivateCache()
|
||||
|
||||
if r.isCached(ip) || r.clients.exists(ip, ClientSourceRDNS) {
|
||||
@@ -107,9 +108,9 @@ func (r *RDNS) Begin(ip net.IP) {
|
||||
|
||||
select {
|
||||
case r.ipCh <- ip:
|
||||
log.Tracef("rdns: %q added to queue", ip)
|
||||
log.Debug("rdns: %q added to queue", ip)
|
||||
default:
|
||||
log.Tracef("rdns: queue is full")
|
||||
log.Debug("rdns: queue is full")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ func (r *RDNS) workerLoop() {
|
||||
defer log.OnPanic("rdns")
|
||||
|
||||
for ip := range r.ipCh {
|
||||
host, err := r.exchanger.Exchange(ip)
|
||||
host, err := r.exchanger.Exchange(ip.AsSlice())
|
||||
if err != nil {
|
||||
log.Debug("rdns: resolving %q: %s", ip, err)
|
||||
|
||||
@@ -128,8 +129,6 @@ func (r *RDNS) workerLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't handle any errors since AddHost doesn't return non-nil errors
|
||||
// for now.
|
||||
_, _ = r.clients.AddHost(ip, host, ClientSourceRDNS)
|
||||
_ = r.clients.AddHost(ip, host, ClientSourceRDNS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
aghtest.ReplaceLogWriter(t, w)
|
||||
|
||||
ip1234, ip1235 := net.IP{1, 2, 3, 4}, net.IP{1, 2, 3, 5}
|
||||
ip1234, ip1235 := netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("1.2.3.5")
|
||||
|
||||
testCases := []struct {
|
||||
cliIDIndex map[string]*Client
|
||||
customChan chan net.IP
|
||||
customChan chan netip.Addr
|
||||
name string
|
||||
wantLog string
|
||||
req net.IP
|
||||
ip netip.Addr
|
||||
wantCacheHit int
|
||||
wantCacheMiss int
|
||||
}{{
|
||||
@@ -42,7 +42,7 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
customChan: nil,
|
||||
name: "cached",
|
||||
wantLog: "",
|
||||
req: ip1234,
|
||||
ip: ip1234,
|
||||
wantCacheHit: 1,
|
||||
wantCacheMiss: 0,
|
||||
}, {
|
||||
@@ -50,7 +50,7 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
customChan: nil,
|
||||
name: "not_cached",
|
||||
wantLog: "rdns: queue is full",
|
||||
req: ip1235,
|
||||
ip: ip1235,
|
||||
wantCacheHit: 0,
|
||||
wantCacheMiss: 1,
|
||||
}, {
|
||||
@@ -58,15 +58,15 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
customChan: nil,
|
||||
name: "already_in_clients",
|
||||
wantLog: "",
|
||||
req: ip1235,
|
||||
ip: ip1235,
|
||||
wantCacheHit: 0,
|
||||
wantCacheMiss: 1,
|
||||
}, {
|
||||
cliIDIndex: map[string]*Client{},
|
||||
customChan: make(chan net.IP, 1),
|
||||
customChan: make(chan netip.Addr, 1),
|
||||
name: "add_to_queue",
|
||||
wantLog: `rdns: "1.2.3.5" added to queue`,
|
||||
req: ip1235,
|
||||
ip: ip1235,
|
||||
wantCacheHit: 0,
|
||||
wantCacheMiss: 1,
|
||||
}}
|
||||
@@ -102,7 +102,7 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rdns.Begin(tc.req)
|
||||
rdns.Begin(tc.ip)
|
||||
assert.Equal(t, tc.wantCacheHit, ipCache.Stats().Hit)
|
||||
assert.Equal(t, tc.wantCacheMiss, ipCache.Stats().Miss)
|
||||
assert.Contains(t, w.String(), tc.wantLog)
|
||||
@@ -179,8 +179,8 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
aghtest.ReplaceLogWriter(t, w)
|
||||
|
||||
localIP := net.IP{192, 168, 1, 1}
|
||||
revIPv4, err := netutil.IPToReversedAddr(localIP)
|
||||
localIP := netip.MustParseAddr("192.168.1.1")
|
||||
revIPv4, err := netutil.IPToReversedAddr(localIP.AsSlice())
|
||||
require.NoError(t, err)
|
||||
|
||||
revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93"))
|
||||
@@ -201,24 +201,24 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
ups upstream.Upstream
|
||||
cliIP netip.Addr
|
||||
wantLog string
|
||||
name string
|
||||
cliIP net.IP
|
||||
}{{
|
||||
ups: locUpstream,
|
||||
cliIP: localIP,
|
||||
wantLog: "",
|
||||
name: "all_good",
|
||||
cliIP: localIP,
|
||||
}, {
|
||||
ups: errUpstream,
|
||||
cliIP: netip.MustParseAddr("192.168.1.2"),
|
||||
wantLog: `rdns: resolving "192.168.1.2": test upstream error`,
|
||||
name: "resolve_error",
|
||||
cliIP: net.IP{192, 168, 1, 2},
|
||||
}, {
|
||||
ups: locUpstream,
|
||||
cliIP: netip.MustParseAddr("2a00:1450:400c:c06::93"),
|
||||
wantLog: "",
|
||||
name: "ipv6_good",
|
||||
cliIP: net.ParseIP("2a00:1450:400c:c06::93"),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -230,7 +230,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
ipToRC: map[netip.Addr]*RuntimeClient{},
|
||||
allTags: stringutil.NewSet(),
|
||||
}
|
||||
ch := make(chan net.IP)
|
||||
ch := make(chan netip.Addr)
|
||||
rdns := &RDNS{
|
||||
exchanger: &rDNSExchanger{
|
||||
ex: tc.ups,
|
||||
|
||||
@@ -40,7 +40,9 @@ type tlsManager struct {
|
||||
conf tlsConfigSettings
|
||||
}
|
||||
|
||||
// newTLSManager initializes the TLS configuration.
|
||||
// newTLSManager initializes the manager of TLS configuration. m is always
|
||||
// non-nil while any returned error indicates that the TLS configuration isn't
|
||||
// valid. Thus TLS may be initialized later, e.g. via the web UI.
|
||||
func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
|
||||
m = &tlsManager{
|
||||
status: &tlsConfigStatus{},
|
||||
@@ -50,7 +52,9 @@ func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
|
||||
if m.conf.Enabled {
|
||||
err = m.load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
m.conf.Enabled = false
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
||||
m.setCertFileTime()
|
||||
@@ -513,6 +517,11 @@ func validateCertChain(certs []*x509.Certificate, srvName string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// errNoIPInCert is the error that is returned from [parseCertChain] if the leaf
|
||||
// certificate doesn't contain IPs.
|
||||
const errNoIPInCert errors.Error = `certificates has no IP addresses; ` +
|
||||
`DNS-over-TLS won't be advertised via DDR`
|
||||
|
||||
// parseCertChain parses the certificate chain from raw data, and returns it.
|
||||
// If ok is true, the returned error, if any, is not critical.
|
||||
func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) {
|
||||
@@ -535,8 +544,7 @@ func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err
|
||||
log.Info("tls: number of certs: %d", len(parsedCerts))
|
||||
|
||||
if !aghtls.CertificateHasIP(parsedCerts[0]) {
|
||||
err = errors.Error(`certificate has no IP addresses` +
|
||||
`, this may cause issues with DNS-over-TLS clients`)
|
||||
err = errNoIPInCert
|
||||
}
|
||||
|
||||
return parsedCerts, true, err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,7 +27,7 @@ const (
|
||||
// WHOIS - module context
|
||||
type WHOIS struct {
|
||||
clients *clientsContainer
|
||||
ipChan chan net.IP
|
||||
ipChan chan netip.Addr
|
||||
|
||||
// dialContext specifies the dial function for creating unencrypted TCP
|
||||
// connections.
|
||||
@@ -51,7 +52,7 @@ func initWHOIS(clients *clientsContainer) *WHOIS {
|
||||
MaxCount: 10000,
|
||||
}),
|
||||
dialContext: customDialContext,
|
||||
ipChan: make(chan net.IP, 255),
|
||||
ipChan: make(chan netip.Addr, 255),
|
||||
}
|
||||
|
||||
go w.workerLoop()
|
||||
@@ -192,7 +193,7 @@ func (w *WHOIS) queryAll(ctx context.Context, target string) (string, error) {
|
||||
}
|
||||
|
||||
// Request WHOIS information
|
||||
func (w *WHOIS) process(ctx context.Context, ip net.IP) (wi *RuntimeClientWHOISInfo) {
|
||||
func (w *WHOIS) process(ctx context.Context, ip netip.Addr) (wi *RuntimeClientWHOISInfo) {
|
||||
resp, err := w.queryAll(ctx, ip.String())
|
||||
if err != nil {
|
||||
log.Debug("whois: error: %s IP:%s", err, ip)
|
||||
@@ -220,24 +221,25 @@ func (w *WHOIS) process(ctx context.Context, ip net.IP) (wi *RuntimeClientWHOISI
|
||||
}
|
||||
|
||||
// Begin - begin requesting WHOIS info
|
||||
func (w *WHOIS) Begin(ip net.IP) {
|
||||
func (w *WHOIS) Begin(ip netip.Addr) {
|
||||
ipBytes := ip.AsSlice()
|
||||
now := uint64(time.Now().Unix())
|
||||
expire := w.ipAddrs.Get([]byte(ip))
|
||||
expire := w.ipAddrs.Get(ipBytes)
|
||||
if len(expire) != 0 {
|
||||
exp := binary.BigEndian.Uint64(expire)
|
||||
if exp > now {
|
||||
return
|
||||
}
|
||||
// TTL expired
|
||||
}
|
||||
|
||||
expire = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(expire, now+whoisTTL)
|
||||
_ = w.ipAddrs.Set([]byte(ip), expire)
|
||||
_ = w.ipAddrs.Set(ipBytes, expire)
|
||||
|
||||
log.Debug("whois: adding %s", ip)
|
||||
|
||||
select {
|
||||
case w.ipChan <- ip:
|
||||
//
|
||||
default:
|
||||
log.Debug("whois: queue is full")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -64,7 +65,7 @@ type Interface interface {
|
||||
|
||||
// GetTopClientIP returns at most limit IP addresses corresponding to the
|
||||
// clients with the most number of requests.
|
||||
TopClientsIP(limit uint) []net.IP
|
||||
TopClientsIP(limit uint) []netip.Addr
|
||||
|
||||
// WriteDiskConfig puts the Interface's configuration to the dc.
|
||||
WriteDiskConfig(dc *DiskConfig)
|
||||
@@ -107,8 +108,6 @@ type StatsCtx struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
var _ Interface = &StatsCtx{}
|
||||
|
||||
// New creates s from conf and properly initializes it. Don't use s before
|
||||
// calling it's Start method.
|
||||
func New(conf Config) (s *StatsCtx, err error) {
|
||||
@@ -178,6 +177,9 @@ func withRecovered(orig *error) {
|
||||
*orig = errors.WithDeferred(*orig, err)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Interface = (*StatsCtx)(nil)
|
||||
|
||||
// Start implements the Interface interface for *StatsCtx.
|
||||
func (s *StatsCtx) Start() {
|
||||
s.initWeb()
|
||||
@@ -250,8 +252,8 @@ func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) {
|
||||
dc.Interval = atomic.LoadUint32(&s.limitHours) / 24
|
||||
}
|
||||
|
||||
// TopClientsIP implements the Interface interface for *StatsCtx.
|
||||
func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) {
|
||||
// TopClientsIP implements the [Interface] interface for *StatsCtx.
|
||||
func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []netip.Addr) {
|
||||
limit := atomic.LoadUint32(&s.limitHours)
|
||||
if limit == 0 {
|
||||
return nil
|
||||
@@ -271,10 +273,10 @@ func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) {
|
||||
}
|
||||
|
||||
a := convertMapToSlice(m, int(maxCount))
|
||||
ips = []net.IP{}
|
||||
ips = []netip.Addr{}
|
||||
for _, it := range a {
|
||||
ip := net.ParseIP(it.Name)
|
||||
if ip != nil {
|
||||
ip, err := netip.ParseAddr(it.Name)
|
||||
if err == nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -45,7 +46,7 @@ func assertSuccessAndUnmarshal(t *testing.T, to any, handler http.Handler, req *
|
||||
}
|
||||
|
||||
func TestStats(t *testing.T) {
|
||||
cliIP := net.IP{127, 0, 0, 1}
|
||||
cliIP := netutil.IPv4Localhost()
|
||||
cliIPStr := cliIP.String()
|
||||
|
||||
handlers := map[string]http.Handler{}
|
||||
@@ -123,7 +124,7 @@ func TestStats(t *testing.T) {
|
||||
topClients := s.TopClientsIP(2)
|
||||
require.NotEmpty(t, topClients)
|
||||
|
||||
assert.True(t, cliIP.Equal(topClients[0]))
|
||||
assert.Equal(t, cliIP, topClients[0])
|
||||
})
|
||||
|
||||
t.Run("reset", func(t *testing.T) {
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
github.com/kyoh86/looppointer v0.1.9
|
||||
github.com/securego/gosec/v2 v2.14.0
|
||||
golang.org/x/tools v0.2.0
|
||||
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16
|
||||
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59
|
||||
honnef.co/go/tools v0.3.3
|
||||
mvdan.cc/gofumpt v0.4.0
|
||||
mvdan.cc/unparam v0.0.0-20220926085101-66de63301820
|
||||
@@ -24,10 +24,10 @@ require (
|
||||
github.com/kyoh86/nolint v0.0.1 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326 // indirect
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -53,10 +53,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326 h1:fl8k2zg28yA23264d82M4dp+YlJ3ngDcpuB1bewkQi4=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221031165847-c99f073a8326/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 h1:962j4VxUJV3GKI6NxKDI9NjATh+tAixlH+9k9MvHSlU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
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.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
@@ -83,8 +83,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -98,8 +98,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16 h1:/H6ddBUaKrFDOBFz0Y3l1/Ppbx19f/rK11jABxiqKFw=
|
||||
golang.org/x/vuln v0.0.0-20221025230227-995372c58a16/go.mod h1:F12iebNzxRMpJsm4W7ape+r/KdnXiSy3VC94WsyCG68=
|
||||
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59 h1:eOOJSuIRc2QwKAgX5qOIhUZJAd2LLKSBfk839dv+Clo=
|
||||
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59/go.mod h1:F12iebNzxRMpJsm4W7ape+r/KdnXiSy3VC94WsyCG68=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
### `GET /control/blocked_services/services` is deprecated
|
||||
|
||||
@@ -94,6 +94,15 @@
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK'
|
||||
'/cache_clear':
|
||||
'post':
|
||||
'tags':
|
||||
- 'global'
|
||||
'operationId': 'cacheClear'
|
||||
'summary': 'Clear DNS cache'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK'
|
||||
'/test_upstream_dns':
|
||||
'post':
|
||||
'tags':
|
||||
|
||||
@@ -188,6 +188,10 @@ After the download you'll find the output locales in the `client/src/__locales/`
|
||||
directory.
|
||||
|
||||
Optional environment:
|
||||
|
||||
* `SLEEP_TIME`: set the sleep time between downloads for `locales:download`,
|
||||
in milliseconds. The default is 250 ms.
|
||||
|
||||
* `UPLOAD_LANGUAGE`: set an alternative language for `locales:upload` to
|
||||
upload.
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ const download = async () => {
|
||||
|
||||
// Don't request the Crowdin API too aggressively to prevent spurious
|
||||
// 400 errors.
|
||||
await sleep(400);
|
||||
const sleepTime = process.env.SLEEP_TIME || 250;
|
||||
await sleep(sleepTime);
|
||||
}
|
||||
|
||||
Promise
|
||||
|
||||
@@ -74,7 +74,11 @@ func main() {
|
||||
Name: f.Name,
|
||||
CategoryID: cat,
|
||||
Homepage: f.Homepage,
|
||||
Source: f.SourceURL,
|
||||
// NOTE: The source URL in filters.json is not guaranteed to contain
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +118,11 @@ type hlFilters struct {
|
||||
|
||||
// hlFiltersFilter is the JSON structure for a filter in the Hostlists Registry.
|
||||
type hlFiltersFilter struct {
|
||||
FilterID string `json:"filterId"`
|
||||
Name string `json:"name"`
|
||||
Homepage string `json:"homepage"`
|
||||
SourceURL string `json:"sourceUrl"`
|
||||
Tags []string `json:"tags"`
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
FilterID string `json:"filterId"`
|
||||
Homepage string `json:"homepage"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// category returns the AdGuard Home category for this filter. If there is no
|
||||
|
||||
Reference in New Issue
Block a user