Compare commits

..

2 Commits

Author SHA1 Message Date
Ainar Garipov
6d3b5c364b all: add tests; imp addrproc, docs 2023-07-14 17:39:42 +03:00
Ainar Garipov
9f93a21bf6 all: imp client resolving 2023-07-13 18:18:49 +03:00
63 changed files with 665 additions and 1272 deletions

View File

@@ -32,33 +32,31 @@
- 'attributes': - 'attributes':
'description': 'On which Platform does the issue occur?' 'description': 'On which Platform does the issue occur?'
'label': 'Platform (OS and CPU architecture)' 'label': 'Platform (OS and CPU architecture)'
# NOTE: Keep the 386 at the bottom for each OS, because a lot of people
# Seem to confuse them with AMD64, which is what they actually need.
'options': 'options':
- 'Darwin (aka macOS), AMD64 (aka x86_64)' - 'Darwin (aka macOS)/AMD64 (aka x86_64)'
- 'Darwin (aka macOS), ARM64' - 'Darwin (aka macOS)/ARM64'
- 'FreeBSD, AMD64 (aka x86_64)' - 'FreeBSD/386'
- 'FreeBSD, ARM64' - 'FreeBSD/AMD64 (aka x86_64)'
- 'FreeBSD, ARMv5' - 'FreeBSD/ARM64'
- 'FreeBSD, ARMv6' - 'FreeBSD/ARMv5'
- 'FreeBSD, ARMv7' - 'FreeBSD/ARMv6'
- 'FreeBSD, 32-bit Intel (aka 386)' - 'FreeBSD/ARMv7'
- 'Linux, AMD64 (aka x86_64)' - 'Linux/386'
- 'Linux, ARM64' - 'Linux/AMD64 (aka x86_64)'
- 'Linux, ARMv5' - 'Linux/ARM64'
- 'Linux, ARMv6' - 'Linux/ARMv5'
- 'Linux, ARMv7' - 'Linux/ARMv6'
- 'Linux, MIPS LE' - 'Linux/ARMv7'
- 'Linux, MIPS' - 'Linux/MIPS LE'
- 'Linux, MIPS64 LE' - 'Linux/MIPS'
- 'Linux, MIPS64' - 'Linux/MIPS64 LE'
- 'Linux, PPC64 LE' - 'Linux/MIPS64'
- 'Linux, 32-bit Intel (aka 386)' - 'Linux/PPC64 LE'
- 'OpenBSD, AMD64 (aka x86_64)' - 'OpenBSD/AMD64 (aka x86_64)'
- 'OpenBSD, ARM64' - 'OpenBSD/ARM64'
- 'Windows, AMD64 (aka x86_64)' - 'Windows/386'
- 'Windows, ARM64' - 'Windows/AMD64 (aka x86_64)'
- 'Windows, 32-bit Intel (aka 386)' - 'Windows/ARM64'
- 'Custom (please mention in the description)' - 'Custom (please mention in the description)'
'id': 'os' 'id': 'os'
'type': 'dropdown' 'type': 'dropdown'
@@ -144,10 +142,8 @@
'type': 'textarea' 'type': 'textarea'
'validations': 'validations':
'required': false 'required': false
# NOTE: GitHub limits the description length to 200 characters. Also, Markdown 'description': >
# doesn't work here. Open a bug report. Please do not open bug reports for questions or help
'description': | with configuring clients. If you want to ask for help, use the Discussions
For help, use the Discussions section instead. Write the title in English section.
to make it easier for other people to search for duplicates. (Any language
is fine in the body.)
'name': 'Bug' 'name': 'Bug'

View File

@@ -48,11 +48,7 @@
'type': 'textarea' 'type': 'textarea'
'validations': 'validations':
'required': false 'required': false
# NOTE: GitHub limits the description length to 200 characters. Also, Markdown 'description': 'Suggest a feature or an enhancement for AdGuard Home'
# doesn't work here.
'description': |
Write the title in English to make it easier for other people to search for
duplicates. (Any language is fine in the body.)
'labels': 'labels':
- 'feature request' - 'feature request'
'name': 'Feature request or enhancement' 'name': 'Feature request or enhancement'

View File

@@ -27,9 +27,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
- Occasional client information lookup failures that could lead to the DNS - Occasional client information lookup failures that could lead to the DNS
server getting stuck ([#6006]). server getting stuck ([#6006]).
- `bufio.Scanner: token too long` and other errors when trying to add - `bufio.Scanner: token too long` errors when trying to add filtering-rule lists
filtering-rule lists with lines over 1024 bytes long or containing cosmetic with lines over 1024 bytes long ([#6003]).
rules ([#6003]).
### Removed ### Removed

View File

@@ -130,10 +130,3 @@ openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start openapi-show: ; cd ./openapi/ && $(YARN) start
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
# TODO(a.garipov): Consider adding to scripts/ and the common project
# structure.
go-upd-tools:
cd ./internal/tools/ &&\
"$(GO.MACRO)" get -u &&\
"$(GO.MACRO)" mod tidy

View File

@@ -416,8 +416,7 @@ There are three options how you can install an unstable version:
### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a> ### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a>
If you run into any problem or have a suggestion, head to [this page][iss] and If you run into any problem or have a suggestion, head to [this page][iss] and
click on the “New issue” button. Please follow the instructions in the issue click on the “New issue” button.
form carefully and don't forget to start by searching for duplicates.
[iss]: https://github.com/AdguardTeam/AdGuardHome/issues [iss]: https://github.com/AdguardTeam/AdGuardHome/issues

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?", "client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?",
"list_confirm_delete": "Opravdu chcete smazat tento seznam?", "list_confirm_delete": "Opravdu chcete smazat tento seznam?",
"auto_clients_title": "Spuštění klienti", "auto_clients_title": "Spuštění klienti",
"auto_clients_desc": "Informace o IP adresách zařízení, která používají nebo mohou používat AdGuard Home. Tyto informace se získávají z několika zdrojů, včetně souborů hosts, reverzního DNS atd.", "auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home",
"access_title": "Nastavení přístupu", "access_title": "Nastavení přístupu",
"access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home", "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home",
"access_allowed_title": "Povolení klienti", "access_allowed_title": "Povolení klienti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?", "client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?",
"list_confirm_delete": "Sikker på, at du vil slette denne liste?", "list_confirm_delete": "Sikker på, at du vil slette denne liste?",
"auto_clients_title": "Klienter (runtime)", "auto_clients_title": "Klienter (runtime)",
"auto_clients_desc": "Oplysninger om IP-adresser på enheder, som (måske) bruger AdGuard Home. Disse oplysninger indsamles fra flere kilder, herunder hosts-filer, reverse DNS mv.", "auto_clients_desc": "Enheder, som ikke er på listen over Permanente klienter, kan stadig bruge AdGuard Home",
"access_title": "Adgangsindstillinger", "access_title": "Adgangsindstillinger",
"access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes", "access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes",
"access_allowed_title": "Tilladte klienter", "access_allowed_title": "Tilladte klienter",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?", "client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
"list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?", "list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
"auto_clients_title": "Laufzeit-Clients", "auto_clients_title": "Laufzeit-Clients",
"auto_clients_desc": "Informationen über IP-Adressen der Geräten, die AdGuard Home nutzen oder nutzen könnten. Diese Informationen werden aus verschiedenen Quellen gesammelt, darunter Hosts-Dateien, Reverse-DNS usw.", "auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen",
"access_title": "Zugriffsrechte", "access_title": "Zugriffsrechte",
"access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren", "access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren",
"access_allowed_title": "Zugelassene Clients", "access_allowed_title": "Zugelassene Clients",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?", "client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?",
"list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?", "list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?",
"auto_clients_title": "Clientes activos", "auto_clients_title": "Clientes activos",
"auto_clients_desc": "Información sobre las direcciones IP de los dispositivos que usan o pueden usar AdGuard Home. Esta información se recopila de varias fuentes, incluidos ficheros de host, DNS inverso, etc.", "auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home",
"access_title": "Configuración de acceso", "access_title": "Configuración de acceso",
"access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home", "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

@@ -2,21 +2,21 @@
"client_settings": "Päätelaiteasetukset", "client_settings": "Päätelaiteasetukset",
"example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;", "example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;",
"example_upstream_comment": "kommentti.", "example_upstream_comment": "kommentti.",
"upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirtapalvelimia samanaikaisesti.", "upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirran palvelimia samanaikaisesti.",
"parallel_requests": "Rinnakkaiset pyynnöt", "parallel_requests": "Rinnakkaiset pyynnöt",
"load_balancing": "Kuormantasaus", "load_balancing": "Kuormantasaus",
"load_balancing_desc": "Lähetä pyyntö yhdelle ylävirtapalvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.", "load_balancing_desc": "Lähetä pyyntö yhdelle ylävirran palvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.",
"bootstrap_dns": "Bootstrap DNS-palvelimet", "bootstrap_dns": "Bootstrap DNS-palvelimet",
"bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.", "bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.",
"local_ptr_title": "Yksityiset käänteis-DNS-palvelimet", "local_ptr_title": "Yksityiset käänteiset DNS-palvelimet",
"local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-pyyntöjen osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteis-DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.", "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-pyyntöjen osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.",
"local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteis-DNS-resolvereita: {{ip}}.", "local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisDNS-resolvereita: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteis-DNS-resolveria.", "local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteisDNS-resolveria.",
"local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi", "local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi",
"resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä", "resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä",
"resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-pyynnöt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, yvirtapalvelimet päätelaitteille, joilla on julkiset IP-osoitteet).", "resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-pyynnöt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, lähtevät palvelimet päätelaitteille, joilla on julkiset IP-osoitteet).",
"use_private_ptr_resolvers_title": "Käytä yksityisiä käänteis-DNS-resolvereita", "use_private_ptr_resolvers_title": "Käytä yksityisiä käänteisDNS-resolvereita",
"use_private_ptr_resolvers_desc": "Suorita käänteis-DNS-selvitykset paikallisesti tarjotuille osoitteille käyttäen näitä ylävirtapalvelimia. Jos ei käytössä, vastaa AdGuard Home kaikkiin sen tyyppisiin PTR-pyyntöihin NXDOMAIN-arvolla, pois lukien DHCP, /etc/hosts, yms. -tiedoista tunnistettut päätelaitteet.", "use_private_ptr_resolvers_desc": "Suorita käänteiset DNS-selvitykset paikallisesti tarjotuille osoitteille käyttäen näitä ylävirran palvelimia. Jos ei käytössä, vastaa AdGuard Home kaikkiin sen tyyppisiin PTR-pyyntöihin NXDOMAIN-arvolla, pois lukien DHCP, /etc/hosts, yms. -tiedoista tunnistettut päätelaitteet.",
"check_dhcp_servers": "Etsi DHCP-palvelimia", "check_dhcp_servers": "Etsi DHCP-palvelimia",
"save_config": "Tallenna asetukset", "save_config": "Tallenna asetukset",
"enabled_dhcp": "DHCP-palvelin otettiin käyttöön", "enabled_dhcp": "DHCP-palvelin otettiin käyttöön",
@@ -220,7 +220,7 @@
"example_upstream_tcp_port": "tavallinen DNS (TCP, portti);", "example_upstream_tcp_port": "tavallinen DNS (TCP, portti);",
"example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);", "example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);",
"all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla", "all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
"updated_upstream_dns_toast": "Ylävirtapalvelimet tallennettiin", "updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein", "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus", "dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla", "dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?", "client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?",
"list_confirm_delete": "Haluatko varmasti poistaa tämän listan?", "list_confirm_delete": "Haluatko varmasti poistaa tämän listan?",
"auto_clients_title": "Määrittämättömät päätelaitteet", "auto_clients_title": "Määrittämättömät päätelaitteet",
"auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea. Näitä tietoja kertään useista lähteistä, mm. hosts-tiedostoista ja kääteis-DNS:llä.", "auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea.",
"access_title": "Käytön asetukset", "access_title": "Käytön asetukset",
"access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.", "access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
"access_allowed_title": "Sallitut päätelaitteet", "access_allowed_title": "Sallitut päätelaitteet",
@@ -623,7 +623,7 @@
"enter_cache_size": "Syötä välimuistin koko (tavuina)", "enter_cache_size": "Syötä välimuistin koko (tavuina)",
"enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)", "enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)",
"enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)", "enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)",
"cache_ttl_min_override_desc": "Pidennä ylävirtapalvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.", "cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.",
"cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).", "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).",
"ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan", "ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan",
"cache_optimistic": "Optimistinen välimuisti", "cache_optimistic": "Optimistinen välimuisti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} » ?", "client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} » ?",
"list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?", "list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?",
"auto_clients_title": "Clients d'exécution", "auto_clients_title": "Clients d'exécution",
"auto_clients_desc": "Informations sur les adresses IP des appareils qui utilisent ou pourraient utiliser AdGuard Home. Ces informations sont recueillies à partir de plusieurs sources, notamment les fichiers hosts, le DNS inverse, etc.", "auto_clients_desc": "Appareils ne figurant pas sur la liste des clients persistants qui peuvent encore utiliser AdGuard Home.",
"access_title": "Paramètres d'accès", "access_title": "Paramètres d'accès",
"access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home", "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home",
"access_allowed_title": "Clients autorisés", "access_allowed_title": "Clients autorisés",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?", "client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?",
"list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?", "list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?",
"auto_clients_title": "Client in tempo reale", "auto_clients_title": "Client in tempo reale",
"auto_clients_desc": "Informazioni sugli indirizzi IP dei dispositivi che utilizzano o potrebbero utilizzare AdGuard Home. Queste informazioni vengono raccolte da diverse fonti, inclusi file host, DNS inverso, ecc.", "auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home",
"access_title": "Impostazioni di accesso", "access_title": "Impostazioni di accesso",
"access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home", "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home",
"access_allowed_title": "Client permessi", "access_allowed_title": "Client permessi",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?", "client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?",
"list_confirm_delete": "このリストを削除してもよろしいですか?", "list_confirm_delete": "このリストを削除してもよろしいですか?",
"auto_clients_title": "ランタイムクライアント", "auto_clients_title": "ランタイムクライアント",
"auto_clients_desc": "AdGuard Home を使用している、または使用する可能性のあるデバイスの IP アドレスに関する情報です。この情報は、hosts ファイル、リバース DNS など、複数の情報源から収集されます。", "auto_clients_desc": "永続的クライアントのリストに未登録で、AdGuard Homeを使用する場合があるデバイスのリスト。",
"access_title": "アクセス設定", "access_title": "アクセス設定",
"access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。", "access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。",
"access_allowed_title": "許可されたクライアント", "access_allowed_title": "許可されたクライアント",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?", "client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?",
"list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?", "list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?",
"auto_clients_title": "Runtime-clients", "auto_clients_title": "Runtime-clients",
"auto_clients_desc": "Informatie over IP-adressen van apparaten die AdGuard Home gebruiken of kunnen gebruiken. Deze informatie wordt verzameld uit verschillende bronnen, waaronder hosts-bestanden, reverse DNS, enz.", "auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken",
"access_title": "Toegangs instellingen", "access_title": "Toegangs instellingen",
"access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen", "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen",
"access_allowed_title": "Toegestane gebruikers", "access_allowed_title": "Toegestane gebruikers",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?", "client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?",
"list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?", "list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?",
"auto_clients_title": "Uruchomieni klienci", "auto_clients_title": "Uruchomieni klienci",
"auto_clients_desc": "Informacje o adresach IP urządzeń korzystających lub mogących korzystać z AdGuard Home. Te informacje są gromadzone z wielu źródeł takich jak pliki hosta, odwrotna translacja DNS, itp.", "auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home",
"access_title": "Ustawienia dostępu", "access_title": "Ustawienia dostępu",
"access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home", "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home",
"access_allowed_title": "Dozwoleni klienci", "access_allowed_title": "Dozwoleni klienci",
@@ -470,7 +470,7 @@
"setup_dns_privacy_ios_2": "Aplikacja <0>AdGuard dla iOS</0> obsługuje <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.", "setup_dns_privacy_ios_2": "Aplikacja <0>AdGuard dla iOS</0> obsługuje <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
"setup_dns_privacy_other_title": "Inne implementacje", "setup_dns_privacy_other_title": "Inne implementacje",
"setup_dns_privacy_other_1": "Sam AdGuard Home może być bezpiecznym klientem DNS na dowolnej platformie.", "setup_dns_privacy_other_1": "Sam AdGuard Home może być bezpiecznym klientem DNS na dowolnej platformie.",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> obsługuje wszystkie znane bezpieczne protokoły DNS.", "setup_dns_privacy_other_2": "<0>dnsproxy</0> obsługuje wszystkie znane bezpieczne protokoły DNS.\n\n",
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.", "setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.", "setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?", "client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?",
"list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
"auto_clients_title": "Clientes ativos", "auto_clients_title": "Clientes ativos",
"auto_clients_desc": "Informações sobre endereços IP de dispositivos que usam ou podem usar o AdGuard Home. Essas informações são coletadas de várias fontes, incluindo arquivos de hosts, DNS reverso, etc.", "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home",
"access_title": "Configurações de acessos", "access_title": "Configurações de acessos",
"access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?", "client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?",
"list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
"auto_clients_title": "Clientes ativos", "auto_clients_title": "Clientes ativos",
"auto_clients_desc": "Informações sobre endereços IP de dispositivos que estão a utilizar ou podem utilizar o AdGuard Home. Estas informações são recolhidas a partir de várias fontes, incluindo ficheiros hosts, DNS reverso etc.", "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home",
"access_title": "Definições de acesso", "access_title": "Definições de acesso",
"access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

@@ -135,7 +135,7 @@
"number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск", "number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск",
"average_processing_time": "Среднее время обработки запроса", "average_processing_time": "Среднее время обработки запроса",
"average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах", "average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах",
"block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов hosts", "block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов хостов",
"filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.", "filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.",
"use_adguard_browsing_sec": "Включить Безопасную навигацию AdGuard", "use_adguard_browsing_sec": "Включить Безопасную навигацию AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home проверит, включён ли домен в веб-службу безопасности браузера. Он будет использовать API, чтобы выполнить проверку: на сервер отправляется только короткий префикс имени домена SHA256.", "use_adguard_browsing_sec_hint": "AdGuard Home проверит, включён ли домен в веб-службу безопасности браузера. Он будет использовать API, чтобы выполнить проверку: на сервер отправляется только короткий префикс имени домена SHA256.",
@@ -296,7 +296,7 @@
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).", "rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса", "blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса", "blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
"blocking_mode_default": "Стандартный: Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле файлов hosts", "blocking_mode_default": "Стандартный: Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле /etc/hosts-style",
"blocking_mode_refused": "REFUSED: Отвечает с кодом REFUSED", "blocking_mode_refused": "REFUSED: Отвечает с кодом REFUSED",
"blocking_mode_nxdomain": "NXDOMAIN: Отвечает с кодом NXDOMAIN\n", "blocking_mode_nxdomain": "NXDOMAIN: Отвечает с кодом NXDOMAIN\n",
"blocking_mode_null_ip": "Нулевой IP: Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)", "blocking_mode_null_ip": "Нулевой IP: Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?", "client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?",
"list_confirm_delete": "Вы уверены, что хотите удалить этот список?", "list_confirm_delete": "Вы уверены, что хотите удалить этот список?",
"auto_clients_title": "Клиенты (runtime)", "auto_clients_title": "Клиенты (runtime)",
"auto_clients_desc": "Информация об IP-адресах устройств, которые используют или могут использовать AdGuard Home. Эта информация собирается из нескольких источников, включая файлы hosts, обратный DNS и так далее.", "auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home",
"access_title": "Настройки доступа", "access_title": "Настройки доступа",
"access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home", "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home",
"access_allowed_title": "Разрешённые клиенты", "access_allowed_title": "Разрешённые клиенты",

View File

@@ -387,7 +387,7 @@
"encryption_key": "Súkromný kľúč", "encryption_key": "Súkromný kľúč",
"encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.", "encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.",
"encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)", "encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)",
"encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať dopyty cez DNS-cez-HTTPS a DNS-cez-TLS.", "encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať požiadavky cez DNS-cez-HTTPS a DNS-cez-TLS.",
"encryption_chain_valid": "Certifikačný reťazec je platný", "encryption_chain_valid": "Certifikačný reťazec je platný",
"encryption_chain_invalid": "Certifikačný reťazec je neplatný", "encryption_chain_invalid": "Certifikačný reťazec je neplatný",
"encryption_key_valid": "Toto je platný {{type}} súkromný kľúč", "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč",
@@ -497,7 +497,7 @@
"blocked_services": "Blokované služby", "blocked_services": "Blokované služby",
"blocked_services_desc": "Umožňuje rýchlo blokovať populárne stránky a služby.", "blocked_services_desc": "Umožňuje rýchlo blokovať populárne stránky a služby.",
"blocked_services_saved": "Blokované služby boli úspešne uložené", "blocked_services_saved": "Blokované služby boli úspešne uložené",
"blocked_services_global": "Použiť globálne blokované služby", "blocked_services_global": "Použite globálne blokované služby",
"blocked_service": "Blokované služby", "blocked_service": "Blokované služby",
"block_all": "Blokovať všetko", "block_all": "Blokovať všetko",
"unblock_all": "Odblokovať všetko", "unblock_all": "Odblokovať všetko",
@@ -554,7 +554,7 @@
"whois": "WHOIS", "whois": "WHOIS",
"filtering_rules_learn_more": "<0>Dozvedieť sa viac</0> o tvorbe vlastných zoznamov hostiteľov.", "filtering_rules_learn_more": "<0>Dozvedieť sa viac</0> o tvorbe vlastných zoznamov hostiteľov.",
"blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi", "blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi",
"blocked_by_cname_or_ip": "Blokované pomocou CNAME alebo IP", "blocked_by_cname_or_ip": "Zablokované na základe CNAME alebo IP",
"try_again": "Skúste znova", "try_again": "Skúste znova",
"domain_desc": "Zadajte meno domény alebo zástupný znak, ktorý chcete prepísať.", "domain_desc": "Zadajte meno domény alebo zástupný znak, ktorý chcete prepísať.",
"example_rewrite_domain": "prepísať odpovede iba pre toto meno domény.", "example_rewrite_domain": "prepísať odpovede iba pre toto meno domény.",
@@ -571,7 +571,7 @@
"autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>", "autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>",
"autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.", "autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.",
"tags_title": "Tagy", "tags_title": "Tagy",
"tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrácie, aby ste ich použili presnejšie. <0>Viac informácií</0>.", "tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrovania, aby ste ich použili presnejšie. <0>Viac informácií</0>.",
"form_select_tags": "Zvoľte tagy klienta", "form_select_tags": "Zvoľte tagy klienta",
"check_title": "Skontrolujte filtráciu", "check_title": "Skontrolujte filtráciu",
"check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.", "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.",
@@ -608,7 +608,7 @@
"show_whitelisted_responses": "Obsiahnuté v bielej listine", "show_whitelisted_responses": "Obsiahnuté v bielej listine",
"show_processed_responses": "Spracované", "show_processed_responses": "Spracované",
"blocked_safebrowsing": "Zablokované modulom Bezpečné prehliadanie", "blocked_safebrowsing": "Zablokované modulom Bezpečné prehliadanie",
"blocked_adult_websites": "Zablokované Rodičovskou kontrolou", "blocked_adult_websites": "Zablokovaná stránka pre dospelých",
"blocked_threats": "Zablokované hrozby", "blocked_threats": "Zablokované hrozby",
"allowed": "Povolené", "allowed": "Povolené",
"filtered": "Filtrované", "filtered": "Filtrované",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?", "client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?",
"list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?", "list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?",
"auto_clients_title": "Odjemalci izvajanja", "auto_clients_title": "Odjemalci izvajanja",
"auto_clients_desc": "Informacije o naslovih IP naprav, ki uporabljajo ali bi lahko uporabljale AdGuard Home. Te informacije so zbrane iz več virov, vključno z datotekami gostiteljev, povratnim DNS-jem itd.", "auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home",
"access_title": "Nastavitve dostopa", "access_title": "Nastavitve dostopa",
"access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home", "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home",
"access_allowed_title": "Dovoljeni odjemalci", "access_allowed_title": "Dovoljeni odjemalci",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?", "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?", "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"auto_clients_title": "Çalışma zamanı istemcileri", "auto_clients_title": "Çalışma zamanı istemcileri",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dahil olmak üzere çeşitli kaynaklardan toplanır.", "auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar",
"access_title": "Erişim ayarları", "access_title": "Erişim ayarları",
"access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz", "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler", "access_allowed_title": "İzin verilen istemciler",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "您确定要删除客户端 \"{{key}}\"", "client_confirm_delete": "您确定要删除客户端 \"{{key}}\"",
"list_confirm_delete": "您确定要删除此列表吗?", "list_confirm_delete": "您确定要删除此列表吗?",
"auto_clients_title": "客户端(运行时间)", "auto_clients_title": "客户端(运行时间)",
"auto_clients_desc": "有关正在使用或可能使用 AdGuard Home 的设备的 IP 地址的信息。此信息是从多个来源收集的,包括 hosts 文件、反向 DNS 等。", "auto_clients_desc": "不在可继续使用 AdGuard Home 的持久客户端列表中的设备。",
"access_title": "访问设置", "access_title": "访问设置",
"access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则", "access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则",
"access_allowed_title": "允许的客户端", "access_allowed_title": "允许的客户端",

View File

@@ -164,7 +164,7 @@
"disabled_parental_toast": "已停用家長監護", "disabled_parental_toast": "已停用家長監護",
"enabled_parental_toast": "已啟用家長監護", "enabled_parental_toast": "已啟用家長監護",
"disabled_safe_search_toast": "已停用安全搜尋", "disabled_safe_search_toast": "已停用安全搜尋",
"updated_save_search_toast": "已更新安全搜尋設定", "enabled_save_search_toast": "已啟用安全搜尋",
"enabled_table_header": "啟用", "enabled_table_header": "啟用",
"name_table_header": "名稱", "name_table_header": "名稱",
"list_url_table_header": "清單 URL 網址", "list_url_table_header": "清單 URL 網址",
@@ -468,7 +468,6 @@
"rewrite_added": "「{{key}}」的 DNS 覆寫新增成功", "rewrite_added": "「{{key}}」的 DNS 覆寫新增成功",
"rewrite_deleted": "「{{key}}」的 DNS 覆寫刪除成功", "rewrite_deleted": "「{{key}}」的 DNS 覆寫刪除成功",
"rewrite_add": "新增 DNS 覆寫", "rewrite_add": "新增 DNS 覆寫",
"rewrite_edit": "編輯 DNS 覆寫",
"rewrite_not_found": "找不到 DNS 覆寫", "rewrite_not_found": "找不到 DNS 覆寫",
"rewrite_confirm_delete": "您確定要刪除 \"{{key}}\" 的 DNS 覆寫?", "rewrite_confirm_delete": "您確定要刪除 \"{{key}}\" 的 DNS 覆寫?",
"rewrite_desc": "提供簡單的方式對特定網域自訂 DNS 回應。", "rewrite_desc": "提供簡單的方式對特定網域自訂 DNS 回應。",
@@ -502,7 +501,6 @@
"interval_days": "{{count}} 天", "interval_days": "{{count}} 天",
"interval_days_plural": "{{count}} 天", "interval_days_plural": "{{count}} 天",
"domain": "網域", "domain": "網域",
"ecs": "EDNS 子網",
"punycode": "Punycode", "punycode": "Punycode",
"answer": "回應", "answer": "回應",
"filter_added_successfully": "已成功新增清單", "filter_added_successfully": "已成功新增清單",
@@ -516,8 +514,6 @@
"statistics_retention_confirm": "您確定要更改統計資料保存時間嗎?如果您縮短期限部分資料可能將會遺失", "statistics_retention_confirm": "您確定要更改統計資料保存時間嗎?如果您縮短期限部分資料可能將會遺失",
"statistics_cleared": "已清除統計資料", "statistics_cleared": "已清除統計資料",
"statistics_enable": "啟用統計數據", "statistics_enable": "啟用統計數據",
"ignore_domains": "已忽略網域(每行一個)",
"ignore_domains_title": "已忽略網域",
"interval_hours": "{{count}} 小時", "interval_hours": "{{count}} 小時",
"interval_hours_plural": "{{count}} 小時", "interval_hours_plural": "{{count}} 小時",
"filters_configuration": "過濾器設定", "filters_configuration": "過濾器設定",
@@ -630,7 +626,6 @@
"safe_browsing": "安全瀏覽", "safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取回應)</i>", "served_from_cache": "{{value}} <i>(由快取回應)</i>",
"form_error_password_length": "密碼必須至少 {{value}} 個字元長度", "form_error_password_length": "密碼必須至少 {{value}} 個字元長度",
"make_static": "新增為靜態",
"theme_dark_desc": "深色主題", "theme_dark_desc": "深色主題",
"theme_light_desc": "淺色主題", "theme_light_desc": "淺色主題",
"disable_for_seconds": "{{count}} 秒", "disable_for_seconds": "{{count}} 秒",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?", "client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?",
"list_confirm_delete": "您確定您想要刪除該清單嗎?", "list_confirm_delete": "您確定您想要刪除該清單嗎?",
"auto_clients_title": "執行時期用戶端", "auto_clients_title": "執行時期用戶端",
"auto_clients_desc": "AdGuard Home 使用或可能使用的裝置的 IP 地址資訊。這些資訊來自多個來源,包括主機檔案、反向 DNS 等。", "auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置",
"access_title": "存取設定", "access_title": "存取設定",
"access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則", "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則",
"access_allowed_title": "已允許的用戶端", "access_allowed_title": "已允許的用戶端",

View File

@@ -64,6 +64,12 @@ export default {
"homepage": "https://github.com/MasterKia/PersianBlocker", "homepage": "https://github.com/MasterKia/PersianBlocker",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.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://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"
},
"KOR_list_kr": { "KOR_list_kr": {
"name": "KOR: List-KR DNS", "name": "KOR: List-KR DNS",
"categoryId": "regional", "categoryId": "regional",
@@ -160,20 +166,14 @@ export default {
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt"
}, },
"dandelion_sprouts_anti_push_notifications": {
"name": "Dandelion Sprout's Anti Push Notifications",
"categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_39.txt"
},
"dandelion_sprouts_game_console_adblock_list": { "dandelion_sprouts_game_console_adblock_list": {
"name": "Dandelion Sprout's Game Console Adblock List", "name": "Dandelion Sprout's Game Console Adblock List",
"categoryId": "other", "categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt"
}, },
"hagezi_multinormal": { "hagezi_personal": {
"name": "HaGeZi Multi NORMAL", "name": "HaGeZi Personal Black \u0026 White",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/hagezi/dns-blocklists", "homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"

View File

@@ -1,5 +1,5 @@
{ {
"timeUpdated": "2023-07-15T00:10:47.501Z", "timeUpdated": "2023-07-01T00:11:37.465Z",
"categories": { "categories": {
"0": "audio_video_player", "0": "audio_video_player",
"1": "comments", "1": "comments",
@@ -14088,11 +14088,10 @@
"companyId": "qihoo_360_technology" "companyId": "qihoo_360_technology"
}, },
"qq.com": { "qq.com": {
"name": "QQ International", "name": "qq.com",
"categoryId": 2, "categoryId": 8,
"url": "https://www.qq.com/", "url": "http://www.qq.com/",
"companyId": "tencent", "companyId": "qq.com"
"source": "AdGuard"
}, },
"qrius": { "qrius": {
"name": "Qrius", "name": "Qrius",
@@ -20509,7 +20508,6 @@
"amazon.com.au": "amazon", "amazon.com.au": "amazon",
"amazon-corp.com": "amazon", "amazon-corp.com": "amazon",
"a2z.com": "amazon", "a2z.com": "amazon",
"firetvcaptiveportal.com": "amazon",
"amazon-adsystem.com": "amazon_adsystem", "amazon-adsystem.com": "amazon_adsystem",
"serving-sys.com": "amazon_adsystem", "serving-sys.com": "amazon_adsystem",
"sizmek.com": "amazon_adsystem", "sizmek.com": "amazon_adsystem",
@@ -22999,7 +22997,6 @@
"mrskincash.com": "mrskincash", "mrskincash.com": "mrskincash",
"e-msedge.net": "msedge", "e-msedge.net": "msedge",
"l-msedge.net": "msedge", "l-msedge.net": "msedge",
"s-msedge.net": "msedge",
"msn.com": "msn", "msn.com": "msn",
"s-msn.com": "msn", "s-msn.com": "msn",
"musculahq.appspot.com": "muscula", "musculahq.appspot.com": "muscula",

5
go.mod
View File

@@ -3,7 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
go 1.19 go 1.19
require ( require (
github.com/AdguardTeam/dnsproxy v0.52.0 // TODO(a.garipov): Update to a tagged version when it's released.
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768
github.com/AdguardTeam/golibs v0.13.4 github.com/AdguardTeam/golibs v0.13.4
github.com/AdguardTeam/urlfilter v0.16.1 github.com/AdguardTeam/urlfilter v0.16.1
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
@@ -27,7 +28,7 @@ require (
// own code for that. Perhaps, use gopacket. // own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0 github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.55 github.com/miekg/dns v1.1.55
github.com/quic-go/quic-go v0.36.1 github.com/quic-go/quic-go v0.35.1
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.0 github.com/ti-mo/netfilter v0.5.0
go.etcd.io/bbolt v1.3.7 go.etcd.io/bbolt v1.3.7

8
go.sum
View File

@@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.52.0 h1:uZxCXflHSAwtJ7uTYXP6qgWcxaBsH0pJvldpwTqIDJk= github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768 h1:5Ia6wA+tqAlTyzuaOVGSlHmb0osLWXeJUs3NxCuC4gA=
github.com/AdguardTeam/dnsproxy v0.52.0/go.mod h1:Jo2zeRe97Rxt3yikXc+fn0LdLtqCj0Xlyh1PNBj6bpM= github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.13.4 h1:ACTwIR1pEENBijHcEWtiMbSh4wWQOlIHRxmUB8oBHf8= github.com/AdguardTeam/golibs v0.13.4 h1:ACTwIR1pEENBijHcEWtiMbSh4wWQOlIHRxmUB8oBHf8=
@@ -108,8 +108,8 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY= github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ= github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@@ -4,13 +4,9 @@ import (
"context" "context"
"io" "io"
"io/fs" "io/fs"
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh" "github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/dnsproxy/upstream"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -139,59 +135,6 @@ func (s *ServiceWithConfig[ConfigType]) Config() (c ConfigType) {
return s.OnConfig() return s.OnConfig()
} }
// Package client
// AddressProcessor is a fake [client.AddressProcessor] implementation for
// tests.
type AddressProcessor struct {
OnProcess func(ip netip.Addr)
OnClose func() (err error)
}
// type check
var _ client.AddressProcessor = (*AddressProcessor)(nil)
// Process implements the [client.AddressProcessor] interface for
// *AddressProcessor.
func (p *AddressProcessor) Process(ip netip.Addr) {
p.OnProcess(ip)
}
// Close implements the [client.AddressProcessor] interface for
// *AddressProcessor.
func (p *AddressProcessor) Close() (err error) {
return p.OnClose()
}
// AddressUpdater is a fake [client.AddressUpdater] implementation for tests.
type AddressUpdater struct {
OnUpdateAddress func(ip netip.Addr, host string, info *whois.Info)
}
// type check
var _ client.AddressUpdater = (*AddressUpdater)(nil)
// UpdateAddress implements the [client.AddressUpdater] interface for
// *AddressUpdater.
func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
p.OnUpdateAddress(ip, host, info)
}
// Package rdns
// Exchanger is a fake [rdns.Exchanger] implementation for tests.
type Exchanger struct {
OnExchange func(ip netip.Addr) (host string, err error)
}
// type check
var _ rdns.Exchanger = (*Exchanger)(nil)
// Exchange implements [rdns.Exchanger] interface for *Exchanger.
func (e *Exchanger) Exchange(ip netip.Addr) (host string, err error) {
return e.OnExchange(ip)
}
// Module dnsproxy // Module dnsproxy
// Package upstream // Package upstream

View File

@@ -1,293 +0,0 @@
package client
import (
"context"
"net/netip"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
)
// ErrClosed is returned from [AddressProcessor.Close] if it's closed more than
// once.
const ErrClosed errors.Error = "use of closed address processor"
// AddressProcessor is the interface for types that can process clients.
type AddressProcessor interface {
Process(ip netip.Addr)
Close() (err error)
}
// EmptyAddrProc is an [AddressProcessor] that does nothing.
type EmptyAddrProc struct{}
// type check
var _ AddressProcessor = EmptyAddrProc{}
// Process implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Process(_ netip.Addr) {}
// Close implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Close() (_ error) { return nil }
// DefaultAddrProcConfig is the configuration structure for address processors.
type DefaultAddrProcConfig struct {
// DialContext is used to create TCP connections to WHOIS servers.
// DialContext must not be nil if [DefaultAddrProcConfig.UseWHOIS] is true.
DialContext whois.DialContextFunc
// Exchanger is used to perform rDNS queries. Exchanger must not be nil if
// [DefaultAddrProcConfig.UseRDNS] is true.
Exchanger rdns.Exchanger
// PrivateSubnets are used to determine if an incoming IP address is
// private. It must not be nil.
PrivateSubnets netutil.SubnetSet
// AddressUpdater is used to update the information about a client's IP
// address. It must not be nil.
AddressUpdater AddressUpdater
// InitialAddresses are the addresses that are queued for processing
// immediately by [NewDefaultAddrProc].
InitialAddresses []netip.Addr
// UseRDNS, if true, enables resolving of client IP addresses using reverse
// DNS.
UseRDNS bool
// UsePrivateRDNS, if true, enables resolving of private client IP addresses
// using reverse DNS. See [DefaultAddrProcConfig.PrivateSubnets].
UsePrivateRDNS bool
// UseWHOIS, if true, enables resolving of client IP addresses using WHOIS.
UseWHOIS bool
}
// AddressUpdater is the interface for storages of DNS clients that can update
// information about them.
//
// TODO(a.garipov): Consider using the actual client storage once it is moved
// into this package.
type AddressUpdater interface {
// UpdateAddress updates information about an IP address, setting host (if
// not empty) and WHOIS information (if not nil).
UpdateAddress(ip netip.Addr, host string, info *whois.Info)
}
// DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if
// configured, and updates that information in a client storage.
type DefaultAddrProc struct {
// clientIPsMu serializes closure of clientIPs and access to isClosed.
clientIPsMu *sync.Mutex
// clientIPs is the channel queueing client processing tasks.
clientIPs chan netip.Addr
// rdns is used to perform rDNS lookups of clients' IP addresses.
rdns rdns.Interface
// whois is used to perform WHOIS lookups of clients' IP addresses.
whois whois.Interface
// addrUpdater is used to update the information about a client's IP
// address.
addrUpdater AddressUpdater
// privateSubnets are used to determine if an incoming IP address is
// private.
privateSubnets netutil.SubnetSet
// isClosed is set to true once the address processor is closed.
isClosed bool
// usePrivateRDNS, if true, enables resolving of private client IP addresses
// using reverse DNS.
usePrivateRDNS bool
}
const (
// defaultQueueSize is the size of queue of IPs for rDNS and WHOIS
// processing.
defaultQueueSize = 255
// defaultCacheSize is the maximum size of the cache for rDNS and WHOIS
// processing. It must be greater than zero.
defaultCacheSize = 10_000
// defaultIPTTL is the Time to Live duration for IP addresses cached by
// rDNS and WHOIS.
defaultIPTTL = 1 * time.Hour
)
// NewDefaultAddrProc returns a new running client address processor. c must
// not be nil.
func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
p = &DefaultAddrProc{
clientIPsMu: &sync.Mutex{},
clientIPs: make(chan netip.Addr, defaultQueueSize),
rdns: &rdns.Empty{},
addrUpdater: c.AddressUpdater,
whois: &whois.Empty{},
privateSubnets: c.PrivateSubnets,
usePrivateRDNS: c.UsePrivateRDNS,
}
if c.UseRDNS {
p.rdns = rdns.New(&rdns.Config{
Exchanger: c.Exchanger,
CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL,
})
}
if c.UseWHOIS {
p.whois = newWHOIS(c.DialContext)
}
go p.process()
for _, ip := range c.InitialAddresses {
p.Process(ip)
}
return p
}
// newWHOIS returns a whois.Interface instance using the given function for
// dialing.
func newWHOIS(dialFunc whois.DialContextFunc) (w whois.Interface) {
// TODO(s.chzhen): Consider making configurable.
const (
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultMaxConnReadSize is an upper limit in bytes for reading from a
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
)
return whois.New(&whois.Config{
DialContext: dialFunc,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
}
// type check
var _ AddressProcessor = (*DefaultAddrProc)(nil)
// Process implements the [AddressProcessor] interface for *DefaultAddrProc.
func (p *DefaultAddrProc) Process(ip netip.Addr) {
p.clientIPsMu.Lock()
defer p.clientIPsMu.Unlock()
if p.isClosed {
return
}
select {
case p.clientIPs <- ip:
// Go on.
default:
log.Debug("clients: ip channel is full; len: %d", len(p.clientIPs))
}
}
// process processes the incoming client IP-address information. It is intended
// to be used as a goroutine. Once clientIPs is closed, process exits.
func (p *DefaultAddrProc) process() {
defer log.OnPanic("addrProcessor.process")
log.Info("clients: processing addresses")
for ip := range p.clientIPs {
host := p.processRDNS(ip)
info := p.processWHOIS(ip)
p.addrUpdater.UpdateAddress(ip, host, info)
}
log.Info("clients: finished processing addresses")
}
// processRDNS resolves the clients' IP addresses using reverse DNS. host is
// empty if there were errors or if the information hasn't changed.
func (p *DefaultAddrProc) processRDNS(ip netip.Addr) (host string) {
start := time.Now()
log.Debug("clients: processing %s with rdns", ip)
defer func() {
log.Debug("clients: finished processing %s with rdns in %s", ip, time.Since(start))
}()
ok := p.shouldResolve(ip)
if !ok {
return
}
host, changed := p.rdns.Process(ip)
if !changed {
host = ""
}
return host
}
// shouldResolve returns false if ip is a loopback address, or ip is private and
// resolving of private addresses is disabled.
func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
return !ip.IsLoopback() &&
(p.usePrivateRDNS || !p.privateSubnets.Contains(ip.AsSlice()))
}
// processWHOIS looks up the information about clients' IP addresses in the
// WHOIS databases. info is nil if there were errors or if the information
// hasn't changed.
func (p *DefaultAddrProc) processWHOIS(ip netip.Addr) (info *whois.Info) {
start := time.Now()
log.Debug("clients: processing %s with whois", ip)
defer func() {
log.Debug("clients: finished processing %s with whois in %s", ip, time.Since(start))
}()
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
// context.
info, changed := p.whois.Process(context.Background(), ip)
if !changed {
info = nil
}
return info
}
// Close implements the [AddressProcessor] interface for *DefaultAddrProc.
func (p *DefaultAddrProc) Close() (err error) {
p.clientIPsMu.Lock()
defer p.clientIPsMu.Unlock()
if p.isClosed {
return ErrClosed
}
close(p.clientIPs)
p.isClosed = true
return nil
}

View File

@@ -1,259 +0,0 @@
package client_test
import (
"context"
"io"
"net"
"net/netip"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakenet"
"github.com/stretchr/testify/assert"
)
func TestEmptyAddrProc(t *testing.T) {
t.Parallel()
p := client.EmptyAddrProc{}
assert.NotPanics(t, func() {
p.Process(testIP)
})
assert.NotPanics(t, func() {
err := p.Close()
assert.NoError(t, err)
})
}
func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
t.Parallel()
privateIP := netip.MustParseAddr("192.168.0.1")
testCases := []struct {
rdnsErr error
ip netip.Addr
name string
host string
usePrivate bool
wantUpd bool
}{{
rdnsErr: nil,
ip: testIP,
name: "success",
host: testHost,
usePrivate: false,
wantUpd: true,
}, {
rdnsErr: nil,
ip: testIP,
name: "no_host",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: netip.MustParseAddr("127.0.0.1"),
name: "localhost",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: privateIP,
name: "private_ignored",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: privateIP,
name: "private_processed",
host: "private.example",
usePrivate: true,
wantUpd: true,
}, {
rdnsErr: errors.Error("rdns error"),
ip: testIP,
name: "rdns_error",
host: "",
usePrivate: false,
wantUpd: false,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
updIPCh := make(chan netip.Addr, 1)
updHostCh := make(chan string, 1)
updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
panic("not implemented")
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(ip netip.Addr) (host string, err error) {
return tc.host, tc.rdnsErr
},
},
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
UseRDNS: true,
UsePrivateRDNS: tc.usePrivate,
UseWHOIS: false,
})
testutil.CleanupAndRequireSuccess(t, p.Close)
p.Process(tc.ip)
if !tc.wantUpd {
return
}
gotIP, _ := testutil.RequireReceive(t, updIPCh, testTimeout)
assert.Equal(t, tc.ip, gotIP)
gotHost, _ := testutil.RequireReceive(t, updHostCh, testTimeout)
assert.Equal(t, tc.host, gotHost)
gotInfo, _ := testutil.RequireReceive(t, updInfoCh, testTimeout)
assert.Nil(t, gotInfo)
})
}
}
// newOnUpdateAddress is a test helper that returns a new OnUpdateAddress
// callback using the provided channels if an update is expected and panicking
// otherwise.
func newOnUpdateAddress(
want bool,
ips chan<- netip.Addr,
hosts chan<- string,
infos chan<- *whois.Info,
) (f func(ip netip.Addr, host string, info *whois.Info)) {
return func(ip netip.Addr, host string, info *whois.Info) {
if !want {
panic("got unexpected update")
}
ips <- ip
hosts <- host
infos <- info
}
}
func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
t.Parallel()
testCases := []struct {
wantInfo *whois.Info
exchErr error
name string
wantUpd bool
}{{
wantInfo: &whois.Info{
City: testWHOISCity,
},
exchErr: nil,
name: "success",
wantUpd: true,
}, {
wantInfo: nil,
exchErr: nil,
name: "no_info",
wantUpd: false,
}, {
wantInfo: nil,
exchErr: errors.Error("whois error"),
name: "whois_error",
wantUpd: false,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
whoisConn := &fakenet.Conn{
OnClose: func() (err error) { return nil },
OnRead: func(b []byte) (n int, err error) {
if tc.wantInfo == nil {
return 0, tc.exchErr
}
data := "city: " + tc.wantInfo.City + "\n"
copy(b, data)
return len(data), io.EOF
},
OnSetDeadline: func(_ time.Time) (err error) { return nil },
OnWrite: func(b []byte) (n int, err error) { return len(b), nil },
}
updIPCh := make(chan netip.Addr, 1)
updHostCh := make(chan string, 1)
updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
return whoisConn, nil
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(_ netip.Addr) (host string, err error) {
panic("not implemented")
},
},
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
UseRDNS: false,
UsePrivateRDNS: false,
UseWHOIS: true,
})
testutil.CleanupAndRequireSuccess(t, p.Close)
p.Process(testIP)
if !tc.wantUpd {
return
}
gotIP, _ := testutil.RequireReceive(t, updIPCh, testTimeout)
assert.Equal(t, testIP, gotIP)
gotHost, _ := testutil.RequireReceive(t, updHostCh, testTimeout)
assert.Empty(t, gotHost)
gotInfo, _ := testutil.RequireReceive(t, updInfoCh, testTimeout)
assert.Equal(t, tc.wantInfo, gotInfo)
})
}
}
func TestDefaultAddrProc_Close(t *testing.T) {
t.Parallel()
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{})
err := p.Close()
assert.NoError(t, err)
err = p.Close()
assert.ErrorIs(t, err, client.ErrClosed)
}

View File

@@ -1,5 +0,0 @@
// Package client contains types and logic dealing with AdGuard Home's DNS
// clients.
//
// TODO(a.garipov): Expand.
package client

View File

@@ -1,25 +0,0 @@
package client_test
import (
"net/netip"
"testing"
"time"
"github.com/AdguardTeam/golibs/testutil"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
// testHost is the common hostname for tests.
const testHost = "client.example"
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
// testWHOISCity is the common city for tests.
const testWHOISCity = "Brussels"
// testIP is the common IP address for tests.
var testIP = netip.MustParseAddr("1.2.3.4")

View File

@@ -13,7 +13,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
@@ -272,12 +271,9 @@ type ServerConfig struct {
TCPListenAddrs []*net.TCPAddr // TCP listen address TCPListenAddrs []*net.TCPAddr // TCP listen address
UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config
// AddrProcConf defines the configuration for the client IP processor. // ClientIPs, if not nil, is used to send clients' IP addresses to other
// If nil, [client.EmptyAddrProc] is used. // parts of AdGuard Home that may use it for resolving rDNS, WHOIS, etc.
// ClientIPs chan netip.Addr
// TODO(a.garipov): The use of [client.EmptyAddrProc] is a crutch for tests.
// Remove that.
AddrProcConf *client.DefaultAddrProcConfig
FilteringConfig FilteringConfig
TLSConfig TLSConfig
@@ -305,6 +301,9 @@ type ServerConfig struct {
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64. // DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []netip.Prefix DNS64Prefixes []netip.Prefix
// ResolveClients signals if the RDNS should resolve clients' addresses.
ResolveClients bool
// UsePrivateRDNS defines if the PTR requests for unknown addresses from // UsePrivateRDNS defines if the PTR requests for unknown addresses from
// locally-served networks should be resolved via private PTR resolvers. // locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool UsePrivateRDNS bool
@@ -344,7 +343,6 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
UpstreamConfig: srvConf.UpstreamConfig, UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler, BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest, RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled, EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines), MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64, UseDNS64: srvConf.UseDNS64,

View File

@@ -1,57 +0,0 @@
package dnsforward
import (
"context"
"fmt"
"net"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
// DialContext is a [whois.DialContextFunc] that uses s to resolve hostnames.
func (s *Server) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
log.Debug("dnsforward: dialing %q for network %q", addr, network)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
// TODO(a.garipov): Consider making configurable.
Timeout: time.Minute * 5,
}
if net.ParseIP(host) != nil {
return dialer.DialContext(ctx, network, addr)
}
addrs, err := s.Resolve(host)
if err != nil {
return nil, fmt.Errorf("resolving %q: %w", host, err)
}
log.Debug("dnsforward: resolving %q: %v", host, addrs)
if len(addrs) == 0 {
return nil, fmt.Errorf("no addresses for host %q", host)
}
var dialErrs []error
for _, a := range addrs {
addr = net.JoinHostPort(a.String(), port)
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
dialErrs = append(dialErrs, err)
continue
}
return conn, err
}
// TODO(a.garipov): Use errors.Join in Go 1.20.
return nil, errors.List(fmt.Sprintf("dialing %q", addr), dialErrs...)
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/querylog"
@@ -100,17 +99,12 @@ type Server struct {
// must be a valid domain name plus dots on each side. // must be a valid domain name plus dots on each side.
localDomainSuffix string localDomainSuffix string
ipset ipsetCtx // ClientIPs, if not nil, is used to send clients' IP addresses to other
privateNets netutil.SubnetSet // parts of AdGuard Home that may use it for resolving rDNS, WHOIS, etc.
clientIPs chan<- netip.Addr
// addrProc, if not nil, is used to process clients' IP addresses with rDNS, ipset ipsetCtx
// WHOIS, etc. privateNets netutil.SubnetSet
addrProc client.AddressProcessor
// localResolvers is a DNS proxy instance used to resolve PTR records for
// addresses considered private as per the [privateNets].
//
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
localResolvers *proxy.Proxy localResolvers *proxy.Proxy
sysResolvers aghnet.SystemResolvers sysResolvers aghnet.SystemResolvers
@@ -180,9 +174,6 @@ const (
// NewServer creates a new instance of the dnsforward.Server // NewServer creates a new instance of the dnsforward.Server
// Note: this function must be called only once // Note: this function must be called only once
//
// TODO(a.garipov): How many constructors and initializers does this thing have?
// Refactor!
func NewServer(p DNSCreateParams) (s *Server, err error) { func NewServer(p DNSCreateParams) (s *Server, err error) {
var localDomainSuffix string var localDomainSuffix string
if p.LocalDomain == "" { if p.LocalDomain == "" {
@@ -270,25 +261,14 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS) c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS)
} }
// LocalPTRResolvers returns the current local PTR resolver configuration. // RDNSSettings returns the copy of actual RDNS configuration.
func (s *Server) LocalPTRResolvers() (localPTRResolvers []string) { func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients, resolvePTR bool) {
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
return stringutil.CloneSlice(s.conf.LocalPTRResolvers) return stringutil.CloneSlice(s.conf.LocalPTRResolvers),
} s.conf.ResolveClients,
s.conf.UsePrivateRDNS
// AddrProcConfig returns the current address processing configuration. Only
// fields c.UsePrivateRDNS, c.UseRDNS, and c.UseWHOIS are filled.
func (s *Server) AddrProcConfig() (c *client.DefaultAddrProcConfig) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return &client.DefaultAddrProcConfig{
UsePrivateRDNS: s.conf.UsePrivateRDNS,
UseRDNS: s.conf.AddrProcConf.UseRDNS,
UseWHOIS: s.conf.AddrProcConf.UseWHOIS,
}
} }
// Resolve - get IP addresses by host name from an upstream server. // Resolve - get IP addresses by host name from an upstream server.
@@ -320,6 +300,10 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
if !s.conf.ResolveClients {
return "", nil
}
arpa, err := netutil.IPToReversedAddr(ip.AsSlice()) arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
if err != nil { if err != nil {
return "", fmt.Errorf("reversing ip: %w", err) return "", fmt.Errorf("reversing ip: %w", err)
@@ -346,7 +330,7 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
} }
var resolver *proxy.Proxy var resolver *proxy.Proxy
if s.privateNets.Contains(ip.AsSlice()) { if s.isPrivateIP(ip) {
if !s.conf.UsePrivateRDNS { if !s.conf.UsePrivateRDNS {
return "", nil return "", nil
} }
@@ -385,6 +369,27 @@ func hostFromPTR(resp *dns.Msg) (host string, err error) {
return "", ErrRDNSNoData return "", ErrRDNSNoData
} }
// isPrivateIP returns true if the ip is private.
func (s *Server) isPrivateIP(ip netip.Addr) (ok bool) {
return s.privateNets.Contains(ip.AsSlice())
}
// ShouldResolveClient returns false if ip is a loopback address, or ip is
// private and resolving of private addresses is disabled.
func (s *Server) ShouldResolveClient(ip netip.Addr) (ok bool) {
if ip.IsLoopback() {
return false
}
isPrivate := s.isPrivateIP(ip)
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return s.conf.ResolveClients &&
(s.conf.UsePrivateRDNS || !isPrivate)
}
// Start starts the DNS server. // Start starts the DNS server.
func (s *Server) Start() error { func (s *Server) Start() error {
s.serverLock.Lock() s.serverLock.Lock()
@@ -457,27 +462,23 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil
} }
// setupLocalResolvers initializes the resolvers for local addresses. For // setupResolvers initializes the resolvers for local addresses. For internal
// internal use only. // use only.
func (s *Server) setupLocalResolvers() (err error) { func (s *Server) setupResolvers(localAddrs []string) (err error) {
bootstraps := s.conf.BootstrapDNS bootstraps := s.conf.BootstrapDNS
resolvers := s.conf.LocalPTRResolvers if len(localAddrs) == 0 {
localAddrs = s.sysResolvers.Get()
if len(resolvers) == 0 {
resolvers = s.sysResolvers.Get()
bootstraps = nil bootstraps = nil
} else {
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
} }
resolvers, err = s.filterOurDNSAddrs(resolvers) localAddrs, err = s.filterOurDNSAddrs(localAddrs)
if err != nil { if err != nil {
return err return err
} }
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers) log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", localAddrs)
uc, err := s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{ upsConfig, err := s.prepareUpstreamConfig(localAddrs, nil, &upstream.Options{
Bootstrap: bootstraps, Bootstrap: bootstraps,
Timeout: defaultLocalTimeout, Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates? // TODO(e.burkov): Should we verify server's certificates?
@@ -490,17 +491,10 @@ func (s *Server) setupLocalResolvers() (err error) {
s.localResolvers = &proxy.Proxy{ s.localResolvers = &proxy.Proxy{
Config: proxy.Config{ Config: proxy.Config{
UpstreamConfig: uc, UpstreamConfig: upsConfig,
}, },
} }
if s.conf.UsePrivateRDNS &&
// Only set the upstream config if there are any upstreams. It's safe
// to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
len(uc.Upstreams)+len(uc.DomainReservedUpstreams)+len(uc.SpecifiedDomainUpstreams) > 0 {
s.dnsProxy.PrivateRDNSUpstreamConfig = uc
}
return nil return nil
} }
@@ -550,37 +544,23 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
return fmt.Errorf("preparing access: %w", err) return fmt.Errorf("preparing access: %w", err)
} }
// Set the proxy here because [setupLocalResolvers] sets its values. s.registerHandlers()
//
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
err = s.setupLocalResolvers() // TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
err = s.setupResolvers(s.conf.LocalPTRResolvers)
if err != nil { if err != nil {
return fmt.Errorf("setting up resolvers: %w", err) return fmt.Errorf("setting up resolvers: %w", err)
} }
s.recDetector.clear() if s.conf.UsePrivateRDNS {
proxyConfig.PrivateRDNSUpstreamConfig = s.localResolvers.UpstreamConfig
if s.conf.AddrProcConf == nil {
// TODO(a.garipov): This is a crutch for tests; remove.
s.conf.AddrProcConf = &client.DefaultAddrProcConfig{}
s.addrProc = client.EmptyAddrProc{}
} else {
c := s.conf.AddrProcConf
c.DialContext = s.DialContext
c.PrivateSubnets = s.privateNets
c.UsePrivateRDNS = s.conf.UsePrivateRDNS
s.addrProc = client.NewDefaultAddrProc(s.conf.AddrProcConf)
// Clear the initial addresses to not resolve them again.
//
// TODO(a.garipov): Consider ways of removing this once more client
// logic is moved to package client.
c.InitialAddresses = nil
} }
s.registerHandlers() s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
s.recDetector.clear()
s.clientIPs = s.conf.ClientIPs
return nil return nil
} }
@@ -723,11 +703,9 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
// TODO(a.garipov): This whole piece of API is weird and needs to be remade. // TODO(a.garipov): This whole piece of API is weird and needs to be remade.
if conf == nil { if conf == nil {
conf = &s.conf conf = &s.conf
} else { } else if s.clientIPs != nil {
closeErr := s.addrProc.Close() close(s.clientIPs)
if closeErr != nil { s.clientIPs = nil
log.Error("dnsforward: closing address processor: %s", closeErr)
}
} }
err = s.Prepare(conf) err = s.Prepare(conf)

View File

@@ -1346,7 +1346,9 @@ func TestServer_Exchange(t *testing.T) {
}, },
} }
srv.conf.ResolveClients = true
srv.conf.UsePrivateRDNS = true srv.conf.UsePrivateRDNS = true
srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed) srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
testCases := []struct { testCases := []struct {
@@ -1420,3 +1422,57 @@ func TestServer_Exchange(t *testing.T) {
assert.Empty(t, host) assert.Empty(t, host)
}) })
} }
func TestServer_ShouldResolveClient(t *testing.T) {
srv := &Server{
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
}
testCases := []struct {
ip netip.Addr
want require.BoolAssertionFunc
name string
resolve bool
usePrivate bool
}{{
name: "default",
ip: netip.MustParseAddr("1.1.1.1"),
want: require.True,
resolve: true,
usePrivate: true,
}, {
name: "no_rdns",
ip: netip.MustParseAddr("1.1.1.1"),
want: require.False,
resolve: false,
usePrivate: true,
}, {
name: "loopback",
ip: netip.MustParseAddr("127.0.0.1"),
want: require.False,
resolve: true,
usePrivate: true,
}, {
name: "private_resolve",
ip: netip.MustParseAddr("192.168.0.1"),
want: require.True,
resolve: true,
usePrivate: true,
}, {
name: "private_no_resolve",
ip: netip.MustParseAddr("192.168.0.1"),
want: require.False,
resolve: true,
usePrivate: false,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
srv.conf.ResolveClients = tc.resolve
srv.conf.UsePrivateRDNS = tc.usePrivate
ok := srv.ShouldResolveClient(tc.ip)
tc.want(t, ok)
})
}
}

View File

@@ -124,7 +124,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
cacheMinTTL := s.conf.CacheMinTTL cacheMinTTL := s.conf.CacheMinTTL
cacheMaxTTL := s.conf.CacheMaxTTL cacheMaxTTL := s.conf.CacheMaxTTL
cacheOptimistic := s.conf.CacheOptimistic cacheOptimistic := s.conf.CacheOptimistic
resolveClients := s.conf.AddrProcConf.UseRDNS resolveClients := s.conf.ResolveClients
usePrivateRDNS := s.conf.UsePrivateRDNS usePrivateRDNS := s.conf.UsePrivateRDNS
localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers) localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
@@ -314,6 +314,8 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled) setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled)
setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled) setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled)
setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6) setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6)
setIfNotNil(&s.conf.ResolveClients, dc.ResolveClients)
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS)
return s.setConfigRestartable(dc) return s.setConfigRestartable(dc)
} }
@@ -333,9 +335,6 @@ func setIfNotNil[T any](currentPtr, newPtr *T) (hasSet bool) {
// setConfigRestartable sets the parameters which trigger a restart. // setConfigRestartable sets the parameters which trigger a restart.
// shouldRestart is true if the server should be restarted to apply changes. // shouldRestart is true if the server should be restarted to apply changes.
// s.serverLock is expected to be locked. // s.serverLock is expected to be locked.
//
// TODO(a.garipov): Some of these could probably be updated without a restart.
// Inspect and consider refactoring.
func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
for _, hasSet := range []bool{ for _, hasSet := range []bool{
setIfNotNil(&s.conf.UpstreamDNS, dc.Upstreams), setIfNotNil(&s.conf.UpstreamDNS, dc.Upstreams),
@@ -348,8 +347,6 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL), setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL),
setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL), setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL),
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic), setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
setIfNotNil(&s.conf.AddrProcConf.UseRDNS, dc.ResolveClients),
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS),
} { } {
shouldRestart = shouldRestart || hasSet shouldRestart = shouldRestart || hasSet
if shouldRestart { if shouldRestart {

View File

@@ -171,7 +171,7 @@ const mozillaFQDN = "use-application-dns.net."
// //
// [Section 6.2 of RFC 6761] states that DNS Registries/Registrars must not // [Section 6.2 of RFC 6761] states that DNS Registries/Registrars must not
// grant requests to register test names in the normal way to any person or // grant requests to register test names in the normal way to any person or
// entity, making domain names under the .test TLD free to use in internal // entity, making domain names under the test. TLD free to use in internal
// purposes. // purposes.
// //
// [Section 6.2 of RFC 6761]: https://www.rfc-editor.org/rfc/rfc6761.html#section-6.2 // [Section 6.2 of RFC 6761]: https://www.rfc-editor.org/rfc/rfc6761.html#section-6.2
@@ -222,7 +222,7 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess return resultCodeSuccess
} }
// processClientIP sends the client IP address to s.addrProc, if needed. // processClientIP sends the client IP address to s.clientIPs, if needed.
func (s *Server) processClientIP(addr net.Addr) { func (s *Server) processClientIP(addr net.Addr) {
clientIP := netutil.NetAddrToAddrPort(addr).Addr() clientIP := netutil.NetAddrToAddrPort(addr).Addr()
if clientIP == (netip.Addr{}) { if clientIP == (netip.Addr{}) {
@@ -231,12 +231,17 @@ func (s *Server) processClientIP(addr net.Addr) {
return return
} }
// Do not assign s.addrProc to a local variable to then use, since this lock // Do not assign s.clientIPs to a local variable to then use, since this
// also serializes the closure of s.addrProc. // lock also serializes the closure of s.clientIPs.
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
s.addrProc.Process(clientIP) select {
case s.clientIPs <- clientIP:
// Go on.
default:
log.Debug("dnsforward: client ip channel is nil or full; len: %d", len(s.clientIPs))
}
} }
func (s *Server) setTableHostToIP(t hostToIPTable) { func (s *Server) setTableHostToIP(t hostToIPTable) {
@@ -719,18 +724,6 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
if s.conf.UsePrivateRDNS { if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req) s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil { if err := s.localResolvers.Resolve(pctx); err != nil {
// Generate the server failure if the private upstream configuration
// is empty.
//
// TODO(e.burkov): Get rid of this crutch once the local resolvers
// logic is moved to the dnsproxy completely.
if errors.Is(err, upstream.ErrNoUpstreams) {
pctx.Res = s.genServerFailure(pctx.Req)
// Do not even put into query log.
return resultCodeFinish
}
dctx.err = err dctx.err = err
return resultCodeError return resultCodeError

View File

@@ -76,21 +76,18 @@ func TestServer_ProcessInitial(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
clientIPs := make(chan netip.Addr, 1)
c := ServerConfig{ c := ServerConfig{
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
AAAADisabled: tc.aaaaDisabled, AAAADisabled: tc.aaaaDisabled,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
}, },
ClientIPs: clientIPs,
} }
s := createTestServer(t, &filtering.Config{}, c, nil) s := createTestServer(t, &filtering.Config{}, c, nil)
var gotAddr netip.Addr
s.addrProc = &aghtest.AddressProcessor{
OnProcess: func(ip netip.Addr) { gotAddr = ip },
OnClose: func() (err error) { panic("not implemented") },
}
dctx := &dnsContext{ dctx := &dnsContext{
proxyCtx: &proxy.DNSContext{ proxyCtx: &proxy.DNSContext{
Req: createTestMessageWithType(tc.target, tc.qType), Req: createTestMessageWithType(tc.target, tc.qType),
@@ -101,6 +98,8 @@ func TestServer_ProcessInitial(t *testing.T) {
gotRC := s.processInitial(dctx) gotRC := s.processInitial(dctx)
assert.Equal(t, tc.wantRC, gotRC) assert.Equal(t, tc.wantRC, gotRC)
gotAddr, _ := testutil.RequireReceive(t, clientIPs, testTimeout)
assert.Equal(t, netutil.NetAddrToAddrPort(testClientAddr).Addr(), gotAddr) assert.Equal(t, netutil.NetAddrToAddrPort(testClientAddr).Addr(), gotAddr)
if tc.wantRCode > 0 { if tc.wantRCode > 0 {

View File

@@ -6,9 +6,10 @@ import (
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"io" "io"
"unicode"
"unicode/utf8"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/slices"
) )
// Parser is a filtering-rule parser that collects data, such as the checksum // Parser is a filtering-rule parser that collects data, such as the checksum
@@ -104,11 +105,13 @@ func (p *Parser) processLine(dst io.Writer, line []byte, lineNum int) (n int, er
badIdx, isRule = p.parseLineTitle(trimmed) badIdx, isRule = p.parseLineTitle(trimmed)
} }
if badIdx != -1 { if badIdx != -1 {
badRune, _ := utf8.DecodeRune(trimmed[badIdx:])
return 0, fmt.Errorf( return 0, fmt.Errorf(
"line %d: character %d: likely binary character %q", "line %d: character %d: non-printable character %q",
lineNum, lineNum,
badIdx+bytes.Index(line, trimmed)+1, badIdx+bytes.Index(line, trimmed)+1,
trimmed[badIdx], badRune,
) )
} }
@@ -141,37 +144,41 @@ func hasPrefixFold(b, prefix []byte) (ok bool) {
} }
// parseLine returns true if the parsed line is a filtering rule. line is // parseLine returns true if the parsed line is a filtering rule. line is
// assumed to be trimmed of whitespace characters. badIdx is the index of the // assumed to be trimmed of whitespace characters. nonPrintIdx is the index of
// first character that may indicate that this is a binary file, or -1 if none. // the first non-printable character, if any; if there are none, nonPrintIdx is
// -1.
// //
// A line is considered a rule if it's not empty, not a comment, and contains // A line is considered a rule if it's not empty, not a comment, and contains
// only printable characters. // only printable characters.
func parseLine(line []byte) (badIdx int, isRule bool) { func parseLine(line []byte) (nonPrintIdx int, isRule bool) {
if len(line) == 0 || line[0] == '#' || line[0] == '!' { if len(line) == 0 || line[0] == '#' || line[0] == '!' {
return -1, false return -1, false
} }
badIdx = slices.IndexFunc(line, likelyBinary) nonPrintIdx = bytes.IndexFunc(line, isNotPrintable)
return badIdx, badIdx == -1 return nonPrintIdx, nonPrintIdx == -1
} }
// likelyBinary returns true if b is likely to be a byte from a binary file. // isNotPrintable returns true if r is not a printable character that can be
func likelyBinary(b byte) (ok bool) { // contained in a filtering rule.
return (b < ' ' || b == 0x7f) && b != '\n' && b != '\r' && b != '\t' func isNotPrintable(r rune) (ok bool) {
// Tab isn't included into Unicode's graphic symbols, so include it here
// explicitly.
return r != '\t' && !unicode.IsGraphic(r)
} }
// parseLineTitle is like [parseLine] but additionally looks for a title. line // parseLineTitle is like [parseLine] but additionally looks for a title. line
// is assumed to be trimmed of whitespace characters. // is assumed to be trimmed of whitespace characters.
func (p *Parser) parseLineTitle(line []byte) (badIdx int, isRule bool) { func (p *Parser) parseLineTitle(line []byte) (nonPrintIdx int, isRule bool) {
if len(line) == 0 || line[0] == '#' { if len(line) == 0 || line[0] == '#' {
return -1, false return -1, false
} }
if line[0] != '!' { if line[0] != '!' {
badIdx = slices.IndexFunc(line, likelyBinary) nonPrintIdx = bytes.IndexFunc(line, isNotPrintable)
return badIdx, badIdx == -1 return nonPrintIdx, nonPrintIdx == -1
} }
const titlePattern = "! Title: " const titlePattern = "! Title: "

View File

@@ -77,14 +77,6 @@ func TestParser_Parse(t *testing.T) {
wantTitle: "Test Title", wantTitle: "Test Title",
wantRulesNum: 1, wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked), wantWritten: len(testRuleTextBlocked),
}, {
name: "cosmetic_with_zwnj",
in: testRuleTextCosmetic,
wantDst: testRuleTextCosmetic,
wantErrMsg: "",
wantTitle: "",
wantRulesNum: 1,
wantWritten: len(testRuleTextCosmetic),
}, { }, {
name: "bad_char", name: "bad_char",
in: "! Title: Test Title \n" + in: "! Title: Test Title \n" +
@@ -93,7 +85,7 @@ func TestParser_Parse(t *testing.T) {
wantDst: testRuleTextBlocked, wantDst: testRuleTextBlocked,
wantErrMsg: "line 3: " + wantErrMsg: "line 3: " +
"character 4: " + "character 4: " +
"likely binary character '\\x7f'", "non-printable character '\\x7f'",
wantTitle: "Test Title", wantTitle: "Test Title",
wantRulesNum: 1, wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked), wantWritten: len(testRuleTextBlocked),
@@ -223,14 +215,6 @@ func BenchmarkParser_Parse(b *testing.B) {
require.NoError(b, errSink) require.NoError(b, errSink)
require.NotNil(b, resSink) require.NotNil(b, resSink)
// Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkParser_Parse-16 100000000 128.0 ns/op 48 B/op 1 allocs/op
} }
func FuzzParser_Parse(f *testing.F) { func FuzzParser_Parse(f *testing.F) {
@@ -242,17 +226,15 @@ func FuzzParser_Parse(f *testing.F) {
"! Comment", "! Comment",
"! Title ", "! Title ",
"! Title XXX", "! Title XXX",
testRuleTextBadTab,
testRuleTextBlocked,
testRuleTextCosmetic,
testRuleTextEtcHostsTab, testRuleTextEtcHostsTab,
testRuleTextHTML, testRuleTextHTML,
testRuleTextBlocked,
testRuleTextBadTab,
"1.2.3.4", "1.2.3.4",
"1.2.3.4 etc-hosts.example", "1.2.3.4 etc-hosts.example",
">>>\x00<<<", ">>>\x00<<<",
">>>\x7F<<<", ">>>\x7F<<<",
strings.Repeat("a", rulelist.DefaultRuleBufSize+1), strings.Repeat("a", n+1),
strings.Repeat("a", bufio.MaxScanTokenSize+1),
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@@ -7,13 +7,8 @@ const testTimeout = 1 * time.Second
// Common texts for tests. // Common texts for tests.
const ( const (
testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
testRuleTextBlocked = "||blocked.example^\n"
testRuleTextEtcHostsTab = "0.0.0.0 tab..example^\t# A comment.\n"
testRuleTextHTML = "<!DOCTYPE html>\n" testRuleTextHTML = "<!DOCTYPE html>\n"
testRuleTextBlocked = "||blocked.example^\n"
// testRuleTextCosmetic is a cosmetic rule with a zero-width non-joiner. testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
// testRuleTextEtcHostsTab = "0.0.0.0 tab..example^\t# A comment.\n"
// See https://github.com/AdguardTeam/AdGuardHome/issues/6003.
testRuleTextCosmetic = "||cosmetic.example## :has-text(/\u200c/i)\n"
) )

View File

@@ -1505,6 +1505,7 @@ var blockedServices = []blockedService{{
"||aus.social^", "||aus.social^",
"||awscommunity.social^", "||awscommunity.social^",
"||climatejustice.social^", "||climatejustice.social^",
"||cupoftea.social^",
"||cyberplace.social^", "||cyberplace.social^",
"||defcon.social^", "||defcon.social^",
"||det.social^", "||det.social^",
@@ -1546,12 +1547,12 @@ var blockedServices = []blockedService{{
"||mastodon.social^", "||mastodon.social^",
"||mastodon.uno^", "||mastodon.uno^",
"||mastodon.world^", "||mastodon.world^",
"||mastodon.zaclys.com^",
"||mastodonapp.uk^", "||mastodonapp.uk^",
"||mastodonners.nl^", "||mastodonners.nl^",
"||mastodont.cat^", "||mastodont.cat^",
"||mastodontech.de^", "||mastodontech.de^",
"||mastodontti.fi^", "||mastodontti.fi^",
"||mastouille.fr^",
"||mathstodon.xyz^", "||mathstodon.xyz^",
"||metalhead.club^", "||metalhead.club^",
"||mindly.social^", "||mindly.social^",
@@ -1588,7 +1589,6 @@ var blockedServices = []blockedService{{
"||techhub.social^", "||techhub.social^",
"||theblower.au^", "||theblower.au^",
"||tkz.one^", "||tkz.one^",
"||todon.eu^",
"||toot.aquilenet.fr^", "||toot.aquilenet.fr^",
"||toot.community^", "||toot.community^",
"||toot.funami.tech^", "||toot.funami.tech^",
@@ -1661,7 +1661,6 @@ var blockedServices = []blockedService{{
"||nintendo.jp^", "||nintendo.jp^",
"||nintendo.net^", "||nintendo.net^",
"||nintendo.nl^", "||nintendo.nl^",
"||nintendo.pt^",
"||nintendoswitch.cn^", "||nintendoswitch.cn^",
"||nintendowifi.net^", "||nintendowifi.net^",
}, },
@@ -2161,20 +2160,6 @@ var blockedServices = []blockedService{{
Rules: []string{ Rules: []string{
"||voot.com^", "||voot.com^",
}, },
}, {
ID: "wargaming",
Name: "Wargaming",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 1.998c-5.52 0-10 4.481-10 9.988 0 5.52 4.48 9.996 10 9.996s10-4.476 10-9.996c0-5.507-4.48-9.988-10-9.988zm0 2c4.413 0 8 3.588 8 7.988 0 3.246-1.944 6.04-4.727 7.293.54-1.861.831-3.988.807-6.226l1.414.414a23.648 23.648 0 0 0-2-4.041c-.627 1.347-1.48 2.56-2.52 3.68l1.68-.133c-1.507 2.92-3.134 3.906-5.547 4.013-.386-4.213.12-7.014 2.827-9.04l.386 1.493c.653-.974 1.36-2.12 2.373-2.947-1.506-.6-2.999-.627-4.492-.334.386.16.76.588 1.014.828-3.485 1.662-5.643 4.202-6.744 7.68A7.95 7.95 0 0 1 4 11.986c0-4.4 3.587-7.988 8-7.988z\"/></svg>"),
Rules: []string{
"||wargaming.com^",
"||wargaming.net^",
"||wgcdn.co^",
"||wgcrowd.io^",
"||worldoftanks.com^",
"||worldofwarplanes.com^",
"||worldofwarships.eu^",
"||wotblitz.com^",
},
}, { }, {
ID: "wechat", ID: "wechat",
Name: "WeChat", Name: "WeChat",

145
internal/home/clientaddr.go Normal file
View File

@@ -0,0 +1,145 @@
package home
import (
"context"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/log"
)
// TODO(a.garipov): It is currently hard to add tests for this structure due to
// strong coupling between it and Context.dnsServer with Context.clients.
// Resolve this coupling and add proper testing.
// clientAddrProcessor processes incoming client addresses with rDNS and WHOIS,
// if configured.
type clientAddrProcessor struct {
rdns rdns.Interface
whois whois.Interface
}
const (
// defaultQueueSize is the size of queue of IPs for rDNS and WHOIS
// processing.
defaultQueueSize = 255
// defaultCacheSize is the maximum size of the cache for rDNS and WHOIS
// processing. It must be greater than zero.
defaultCacheSize = 10_000
// defaultIPTTL is the Time to Live duration for IP addresses cached by
// rDNS and WHOIS.
defaultIPTTL = 1 * time.Hour
)
// newClientAddrProcessor returns a new client address processor. c must not be
// nil.
func newClientAddrProcessor(c *clientSourcesConfig) (p *clientAddrProcessor) {
p = &clientAddrProcessor{
rdns: &rdns.Empty{},
whois: &whois.Empty{},
}
if c.RDNS {
p.rdns = rdns.New(&rdns.Config{
Exchanger: Context.dnsServer,
CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL,
})
}
if c.WHOIS {
// TODO(s.chzhen): Consider making configurable.
const (
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultMaxConnReadSize is an upper limit in bytes for reading from a
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
)
p.whois = whois.New(&whois.Config{
DialContext: customDialContext,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
}
return p
}
// process processes the incoming client IP-address information. It is intended
// to be used as a goroutine. Once clientIPs is closed, process exits.
func (p *clientAddrProcessor) process(clientIPs <-chan netip.Addr) {
defer log.OnPanic("clientAddrProcessor.process")
log.Info("home: processing client addresses")
for ip := range clientIPs {
p.processRDNS(ip)
p.processWHOIS(ip)
}
log.Info("home: finished processing client addresses")
}
// processRDNS resolves the clients' IP addresses using reverse DNS.
func (p *clientAddrProcessor) processRDNS(ip netip.Addr) {
start := time.Now()
log.Debug("home: processing client %s with rdns", ip)
defer func() {
log.Debug("home: finished processing client %s with rdns in %s", ip, time.Since(start))
}()
ok := Context.dnsServer.ShouldResolveClient(ip)
if !ok {
return
}
host, changed := p.rdns.Process(ip)
if host == "" || !changed {
return
}
ok = Context.clients.AddHost(ip, host, ClientSourceRDNS)
if ok {
return
}
log.Debug("dns: setting rdns info for client %q: already set with higher priority source", ip)
}
// processWHOIS looks up the information aobut clients' IP addresses in the
// WHOIS databases.
func (p *clientAddrProcessor) processWHOIS(ip netip.Addr) {
start := time.Now()
log.Debug("home: processing client %s with whois", ip)
defer func() {
log.Debug("home: finished processing client %s with whois in %s", ip, time.Since(start))
}()
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
// context.
info, changed := p.whois.Process(context.Background(), ip)
if info == nil || !changed {
return
}
Context.clients.setWHOISInfo(ip, info)
}

View File

@@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
@@ -744,9 +743,11 @@ func (clients *clientsContainer) Update(prev, c *Client) (err error) {
return nil return nil
} }
// setWHOISInfo sets the WHOIS information for a client. clients.lock is // setWHOISInfo sets the WHOIS information for a client.
// expected to be locked.
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) { func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
clients.lock.Lock()
defer clients.lock.Unlock()
_, ok := clients.findLocked(ip.String()) _, ok := clients.findLocked(ip.String())
if ok { if ok {
log.Debug("clients: client for %s is already created, ignore whois info", ip) log.Debug("clients: client for %s is already created, ignore whois info", ip)
@@ -773,11 +774,9 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
rc.WHOIS = wi rc.WHOIS = wi
} }
// addHost adds a new IP-hostname pairing. The priorities of the sources are // AddHost adds a new IP-hostname pairing. The priorities of the sources are
// taken into account. ok is true if the pairing was added. // taken into account. ok is true if the pairing was added.
// func (clients *clientsContainer) AddHost(
// TODO(a.garipov): Only used in internal tests. Consider removing.
func (clients *clientsContainer) addHost(
ip netip.Addr, ip netip.Addr,
host string, host string,
src clientSource, src clientSource,
@@ -788,32 +787,6 @@ func (clients *clientsContainer) addHost(
return clients.addHostLocked(ip, host, src) return clients.addHostLocked(ip, host, src)
} }
// type check
var _ client.AddressUpdater = (*clientsContainer)(nil)
// UpdateAddress implements the [client.AddressUpdater] interface for
// *clientsContainer
func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
// Common fast path optimization.
if host == "" && info == nil {
return
}
clients.lock.Lock()
defer clients.lock.Unlock()
if host != "" {
ok := clients.addHostLocked(ip, host, ClientSourceRDNS)
if !ok {
log.Debug("clients: host for client %q already set with higher priority source", ip)
}
}
if info != nil {
clients.setWHOISInfo(ip, info)
}
}
// addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be // addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be
// locked. // locked.
func (clients *clientsContainer) addHostLocked( func (clients *clientsContainer) addHostLocked(

View File

@@ -168,13 +168,13 @@ func TestClients(t *testing.T) {
t.Run("addhost_success", func(t *testing.T) { t.Run("addhost_success", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host", ClientSourceARP) ok := clients.AddHost(ip, "host", ClientSourceARP)
assert.True(t, ok) assert.True(t, ok)
ok = clients.addHost(ip, "host2", ClientSourceARP) ok = clients.AddHost(ip, "host2", ClientSourceARP)
assert.True(t, ok) assert.True(t, ok)
ok = clients.addHost(ip, "host3", ClientSourceHostsFile) ok = clients.AddHost(ip, "host3", ClientSourceHostsFile)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceHostsFile) assert.Equal(t, clients.clientSource(ip), ClientSourceHostsFile)
@@ -182,18 +182,18 @@ func TestClients(t *testing.T) {
t.Run("dhcp_replaces_arp", func(t *testing.T) { t.Run("dhcp_replaces_arp", func(t *testing.T) {
ip := netip.MustParseAddr("1.2.3.4") ip := netip.MustParseAddr("1.2.3.4")
ok := clients.addHost(ip, "from_arp", ClientSourceARP) ok := clients.AddHost(ip, "from_arp", ClientSourceARP)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceARP) assert.Equal(t, clients.clientSource(ip), ClientSourceARP)
ok = clients.addHost(ip, "from_dhcp", ClientSourceDHCP) ok = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceDHCP) assert.Equal(t, clients.clientSource(ip), ClientSourceDHCP)
}) })
t.Run("addhost_fail", func(t *testing.T) { t.Run("addhost_fail", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host1", ClientSourceRDNS) ok := clients.AddHost(ip, "host1", ClientSourceRDNS)
assert.False(t, ok) assert.False(t, ok)
}) })
} }
@@ -216,7 +216,7 @@ func TestClientsWHOIS(t *testing.T) {
t.Run("existing_auto-client", func(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host", ClientSourceRDNS) ok := clients.AddHost(ip, "host", ClientSourceRDNS)
assert.True(t, ok) assert.True(t, ok)
clients.setWHOISInfo(ip, whois) clients.setWHOISInfo(ip, whois)
@@ -259,7 +259,7 @@ func TestClientsAddExisting(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
// Now add an auto-client with the same IP. // Now add an auto-client with the same IP.
ok = clients.addHost(ip, "test", ClientSourceRDNS) ok = clients.AddHost(ip, "test", ClientSourceRDNS)
assert.True(t, ok) assert.True(t, ok)
}) })

View File

@@ -590,13 +590,7 @@ func (c *configuration) write() (err error) {
s.WriteDiskConfig(&c) s.WriteDiskConfig(&c)
dns := &config.DNS dns := &config.DNS
dns.FilteringConfig = c dns.FilteringConfig = c
dns.LocalPTRResolvers, config.Clients.Sources.RDNS, dns.UsePrivateRDNS = s.RDNSSettings()
dns.LocalPTRResolvers = s.LocalPTRResolvers()
addrProcConf := s.AddrProcConfig()
config.Clients.Sources.RDNS = addrProcConf.UseRDNS
config.Clients.Sources.WHOIS = addrProcConf.UseWHOIS
dns.UsePrivateRDNS = addrProcConf.UsePrivateRDNS
} }
if Context.dhcpServer != nil { if Context.dhcpServer != nil {

View File

@@ -176,16 +176,12 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
// ------------------------ // ------------------------
// registration of handlers // registration of handlers
// ------------------------ // ------------------------
func registerControlHandlers(web *webAPI) { func registerControlHandlers() {
Context.mux.HandleFunc(
"/control/version.json",
postInstall(optionalAuth(web.handleVersionJSON)),
)
httpRegister(http.MethodPost, "/control/update", web.handleUpdate)
httpRegister(http.MethodGet, "/control/status", handleStatus) httpRegister(http.MethodGet, "/control/status", handleStatus)
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleVersionJSON)))
httpRegister(http.MethodPost, "/control/update", handleUpdate)
httpRegister(http.MethodGet, "/control/profile", handleGetProfile) httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
httpRegister(http.MethodPut, "/control/profile/update", handlePutProfile) httpRegister(http.MethodPut, "/control/profile/update", handlePutProfile)

View File

@@ -448,7 +448,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
web.conf.BindHost = req.Web.IP web.conf.BindHost = req.Web.IP
web.conf.BindPort = req.Web.Port web.conf.BindPort = req.Web.Port
registerControlHandlers(web) registerControlHandlers()
aghhttp.OK(w) aghhttp.OK(w)
if f, ok := w.(http.Flusher); ok { if f, ok := w.(http.Flusher); ok {

View File

@@ -29,9 +29,9 @@ type temporaryError interface {
// handleVersionJSON is the handler for the POST /control/version.json HTTP API. // handleVersionJSON is the handler for the POST /control/version.json HTTP API.
// //
// TODO(a.garipov): Find out if this API used with a GET method by anyone. // TODO(a.garipov): Find out if this API used with a GET method by anyone.
func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) { func handleVersionJSON(w http.ResponseWriter, r *http.Request) {
resp := &versionResponse{} resp := &versionResponse{}
if web.conf.disableUpdate { if Context.disableUpdate {
resp.Disabled = true resp.Disabled = true
_ = aghhttp.WriteJSONResponse(w, r, resp) _ = aghhttp.WriteJSONResponse(w, r, resp)
@@ -52,7 +52,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
} }
} }
err = web.requestVersionInfo(resp, req.Recheck) err = requestVersionInfo(resp, req.Recheck)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
aghhttp.Error(r, w, http.StatusBadGateway, "%s", err) aghhttp.Error(r, w, http.StatusBadGateway, "%s", err)
@@ -73,10 +73,9 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
// requestVersionInfo sets the VersionInfo field of resp if it can reach the // requestVersionInfo sets the VersionInfo field of resp if it can reach the
// update server. // update server.
func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err error) { func requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
updater := web.conf.updater
for i := 0; i != 3; i++ { for i := 0; i != 3; i++ {
resp.VersionInfo, err = updater.VersionInfo(recheck) resp.VersionInfo, err = Context.updater.VersionInfo(recheck)
if err != nil { if err != nil {
var terr temporaryError var terr temporaryError
if errors.As(err, &terr) && terr.Temporary() { if errors.As(err, &terr) && terr.Temporary() {
@@ -96,7 +95,7 @@ func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err
} }
if err != nil { if err != nil {
vcu := updater.VersionCheckURL() vcu := Context.updater.VersionCheckURL()
return fmt.Errorf("getting version info from %s: %w", vcu, err) return fmt.Errorf("getting version info from %s: %w", vcu, err)
} }
@@ -105,9 +104,8 @@ func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err
} }
// handleUpdate performs an update to the latest available version procedure. // handleUpdate performs an update to the latest available version procedure.
func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) { func handleUpdate(w http.ResponseWriter, r *http.Request) {
updater := web.conf.updater if Context.updater.NewVersion() == "" {
if updater.NewVersion() == "" {
aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now") aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now")
return return
@@ -124,7 +122,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
return return
} }
err = updater.Update(false) err = Context.updater.Update(false)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
@@ -139,7 +137,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
// The background context is used because the underlying functions wrap it // The background context is used because the underlying functions wrap it
// with timeout and shut down the server, which handles current request. It // with timeout and shut down the server, which handles current request. It
// also should be done in a separate goroutine for the same reason. // also should be done in a separate goroutine for the same reason.
go finishUpdate(context.Background(), execPath, web.conf.runningAsService) go finishUpdate(context.Background(), execPath)
} }
// versionResponse is the response for /control/version.json endpoint. // versionResponse is the response for /control/version.json endpoint.
@@ -180,7 +178,7 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
} }
// finishUpdate completes an update procedure. // finishUpdate completes an update procedure.
func finishUpdate(ctx context.Context, execPath string, runningAsService bool) { func finishUpdate(ctx context.Context, execPath string) {
var err error var err error
log.Info("stopping all tasks") log.Info("stopping all tasks")
@@ -189,7 +187,7 @@ func finishUpdate(ctx context.Context, execPath string, runningAsService bool) {
cleanupAlways() cleanupAlways()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if runningAsService { if Context.runningAsService {
// NOTE: We can't restart the service via "kardianos/service" // NOTE: We can't restart the service via "kardianos/service"
// package, because it kills the process first we can't start a new // package, because it kills the process first we can't start a new
// instance, because Windows doesn't allow it. // instance, because Windows doesn't allow it.

View File

@@ -13,7 +13,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
@@ -133,7 +132,7 @@ func initDNSServer(
return fmt.Errorf("preparing set of private subnets: %w", err) return fmt.Errorf("preparing set of private subnets: %w", err)
} }
Context.dnsServer, err = dnsforward.NewServer(dnsforward.DNSCreateParams{ p := dnsforward.DNSCreateParams{
DNSFilter: filters, DNSFilter: filters,
Stats: sts, Stats: sts,
QueryLog: qlog, QueryLog: qlog,
@@ -141,7 +140,9 @@ func initDNSServer(
Anonymizer: anonymizer, Anonymizer: anonymizer,
LocalDomain: config.DHCP.LocalDomainName, LocalDomain: config.DHCP.LocalDomainName,
DHCPServer: dhcpSrv, DHCPServer: dhcpSrv,
}) }
Context.dnsServer, err = dnsforward.NewServer(p)
if err != nil { if err != nil {
closeDNSServer() closeDNSServer()
@@ -164,6 +165,15 @@ func initDNSServer(
return fmt.Errorf("dnsServer.Prepare: %w", err) return fmt.Errorf("dnsServer.Prepare: %w", err)
} }
clientIPs := dnsConf.ClientIPs
addrProc := newClientAddrProcessor(config.Clients.Sources)
go addrProc.process(clientIPs)
const topClientsNumber = 100
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
clientIPs <- ip
}
return nil return nil
} }
@@ -229,35 +239,18 @@ func newServerConfig(
) (newConf *dnsforward.ServerConfig, err error) { ) (newConf *dnsforward.ServerConfig, err error) {
dnsConf := config.DNS dnsConf := config.DNS
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()}) hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
clientIPs := make(chan netip.Addr, defaultQueueSize)
newConf = &dnsforward.ServerConfig{ newConf = &dnsforward.ServerConfig{
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
FilteringConfig: dnsConf.FilteringConfig, FilteringConfig: dnsConf.FilteringConfig,
ConfigModified: onConfigModified, ConfigModified: onConfigModified,
HTTPRegister: httpReg, HTTPRegister: httpReg,
ClientIPs: clientIPs,
UseDNS64: config.DNS.UseDNS64, UseDNS64: config.DNS.UseDNS64,
DNS64Prefixes: config.DNS.DNS64Prefixes, DNS64Prefixes: config.DNS.DNS64Prefixes,
} }
var initialAddresses []netip.Addr
// Context.stats may be nil here if initDNSServer is called from
// [cmdlineUpdate].
if sts := Context.stats; sts != nil {
const initialClientsNum = 100
initialAddresses = Context.stats.TopClientsIP(initialClientsNum)
}
// Do not set DialContext, PrivateSubnets, and UsePrivateRDNS, because they
// are set by [dnsforward.Server.Prepare].
newConf.AddrProcConf = &client.DefaultAddrProcConfig{
Exchanger: Context.dnsServer,
AddressUpdater: &Context.clients,
InitialAddresses: initialAddresses,
UseRDNS: config.Clients.Sources.RDNS,
UseWHOIS: config.Clients.Sources.WHOIS,
}
if tlsConf.Enabled { if tlsConf.Enabled {
newConf.TLSConfig = tlsConf.TLSConfig newConf.TLSConfig = tlsConf.TLSConfig
newConf.TLSConfig.ServerName = tlsConf.ServerName newConf.TLSConfig.ServerName = tlsConf.ServerName
@@ -293,6 +286,7 @@ func newServerConfig(
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
newConf.ResolveClients = config.Clients.Sources.RDNS
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
newConf.ServeHTTP3 = dnsConf.ServeHTTP3 newConf.ServeHTTP3 = dnsConf.ServeHTTP3
newConf.UseHTTP3Upstreams = dnsConf.UseHTTP3Upstreams newConf.UseHTTP3Upstreams = dnsConf.UseHTTP3Upstreams
@@ -464,6 +458,9 @@ func reconfigureDNSServer() (err error) {
return fmt.Errorf("starting forwarding dns server: %w", err) return fmt.Errorf("starting forwarding dns server: %w", err)
} }
addrProc := newClientAddrProcessor(config.Clients.Sources)
go addrProc.process(newConf.ClientIPs)
return nil return nil
} }

View File

@@ -3,12 +3,14 @@ package home
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/fs" "io/fs"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"net/url"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@@ -64,24 +66,34 @@ type homeContext struct {
// configuration files, for example /etc/hosts. // configuration files, for example /etc/hosts.
etcHosts *aghnet.HostsContainer etcHosts *aghnet.HostsContainer
updater *updater.Updater
// mux is our custom http.ServeMux. // mux is our custom http.ServeMux.
mux *http.ServeMux mux *http.ServeMux
// Runtime properties // Runtime properties
// -- // --
configFilename string // Config filename (can be overridden via the command line arguments) 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 workDir string // Location of our directory, used to protect against CWD being somewhere else
pidFileName string // PID file name. Empty if no PID file was created. pidFileName string // PID file name. Empty if no PID file was created.
controlLock sync.Mutex controlLock sync.Mutex
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2 tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
client *http.Client
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use. // tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16 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 // firstRun, if true, tells AdGuard Home to only start the web interface
// service, and only serve the first-run APIs. // service, and only serve the first-run APIs.
firstRun bool firstRun bool
// runningAsService flag is set to true when options are passed from the service runner
runningAsService bool
} }
// getDataDir returns path to the directory where we store databases and filters // getDataDir returns path to the directory where we store databases and filters
@@ -104,11 +116,11 @@ func Main(clientBuildFS fs.FS) {
// package flag. // package flag.
opts := loadCmdLineOpts() opts := loadCmdLineOpts()
signals := make(chan os.Signal, 1) Context.appSignalChannel = make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() { go func() {
for { for {
sig := <-signals sig := <-Context.appSignalChannel
log.Info("Received signal %q", sig) log.Info("Received signal %q", sig)
switch sig { switch sig {
case syscall.SIGHUP: case syscall.SIGHUP:
@@ -123,7 +135,7 @@ func Main(clientBuildFS fs.FS) {
}() }()
if opts.serviceControlAction != "" { if opts.serviceControlAction != "" {
handleServiceControlAction(opts, clientBuildFS, signals) handleServiceControlAction(opts, clientBuildFS)
return return
} }
@@ -135,48 +147,74 @@ func Main(clientBuildFS fs.FS) {
// setupContext initializes [Context] fields. It also reads and upgrades // setupContext initializes [Context] fields. It also reads and upgrades
// config file if necessary. // config file if necessary.
func setupContext(opts options) (err error) { func setupContext(opts options) (err error) {
Context.firstRun = detectFirstRun() setupContextFlags(opts)
Context.tlsRoots = aghtls.SystemRootCAs() Context.tlsRoots = aghtls.SystemRootCAs()
Context.client = &http.Client{
Timeout: time.Minute * 5,
Transport: &http.Transport{
DialContext: customDialContext,
Proxy: getHTTPProxy,
TLSClientConfig: &tls.Config{
RootCAs: Context.tlsRoots,
CipherSuites: Context.tlsCipherIDs,
MinVersion: tls.VersionTLS12,
},
},
}
Context.mux = http.NewServeMux() Context.mux = http.NewServeMux()
if Context.firstRun { if !Context.firstRun {
log.Info("This is the first time AdGuard Home is launched") // Do the upgrade if necessary.
checkPermissions() err = upgradeConfig()
return nil
}
// Do the upgrade if necessary.
err = upgradeConfig()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
if err = parseConfig(); err != nil {
log.Error("parsing configuration file: %s", err)
os.Exit(1)
}
if opts.checkConfig {
log.Info("configuration file is ok")
os.Exit(0)
}
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
err = setupHostsContainer()
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
return err return err
} }
if err = parseConfig(); err != nil {
log.Error("parsing configuration file: %s", err)
os.Exit(1)
}
if opts.checkConfig {
log.Info("configuration file is ok")
os.Exit(0)
}
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
err = setupHostsContainer()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
}
} }
return nil return nil
} }
// setupContextFlags sets global flags and prints their status to the log.
func setupContextFlags(opts options) {
Context.firstRun = detectFirstRun()
if Context.firstRun {
log.Info("This is the first time AdGuard Home is launched")
checkPermissions()
}
Context.runningAsService = opts.runningAsService
// Don't print the runningAsService flag, since that has already been done
// in [run].
Context.disableUpdate = opts.disableUpdate || version.Channel() == version.ChannelDevelopment
if Context.disableUpdate {
log.Info("AdGuard Home updates are disabled")
}
}
// logIfUnsupported logs a formatted warning if the error is one of the // logIfUnsupported logs a formatted warning if the error is one of the
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns // unsupported errors and returns nil. If err is nil, logIfUnsupported returns
// nil. Otherwise, it returns err. // nil. Otherwise, it returns err.
@@ -281,7 +319,7 @@ func initContextClients() (err error) {
return err return err
} }
//lint:ignore SA1019 Migration is not over. //lint:ignore SA1019 Migration is not over.
config.DHCP.WorkDir = Context.workDir config.DHCP.WorkDir = Context.workDir
config.DHCP.DataDir = Context.getDataDir() config.DHCP.DataDir = Context.getDataDir()
config.DHCP.HTTPRegister = httpRegister config.DHCP.HTTPRegister = httpRegister
@@ -296,6 +334,18 @@ func initContextClients() (err error) {
return fmt.Errorf("initing dhcp: %w", err) return fmt.Errorf("initing dhcp: %w", err)
} }
Context.updater = updater.NewUpdater(&updater.Config{
Client: Context.client,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: Context.workDir,
ConfName: config.getConfigFilename(),
})
var arpdb aghnet.ARPDB var arpdb aghnet.ARPDB
if config.Clients.Sources.ARP { if config.Clients.Sources.ARP {
arpdb = aghnet.NewARPDB() arpdb = aghnet.NewARPDB()
@@ -377,7 +427,7 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
conf.Filters = slices.Clone(config.Filters) conf.Filters = slices.Clone(config.Filters)
conf.WhitelistFilters = slices.Clone(config.WhitelistFilters) conf.WhitelistFilters = slices.Clone(config.WhitelistFilters)
conf.UserRules = slices.Clone(config.UserRules) conf.UserRules = slices.Clone(config.UserRules)
conf.HTTPClient = httpClient() conf.HTTPClient = Context.client
cacheTime := time.Duration(conf.CacheTime) * time.Minute cacheTime := time.Duration(conf.CacheTime) * time.Minute
@@ -459,7 +509,7 @@ func checkPorts() (err error) {
return nil return nil
} }
func initWeb(opts options, clientBuildFS fs.FS, upd *updater.Updater) (web *webAPI, err error) { func initWeb(opts options, clientBuildFS fs.FS) (web *webAPI, err error) {
var clientFS fs.FS var clientFS fs.FS
if opts.localFrontend { if opts.localFrontend {
log.Info("warning: using local frontend files") log.Info("warning: using local frontend files")
@@ -472,16 +522,8 @@ func initWeb(opts options, clientBuildFS fs.FS, upd *updater.Updater) (web *webA
} }
} }
disableUpdate := opts.disableUpdate || version.Channel() == version.ChannelDevelopment webConf := webConfig{
if disableUpdate { firstRun: Context.firstRun,
log.Info("AdGuard Home updates are disabled")
}
webConf := &webConfig{
updater: upd,
clientFS: clientFS,
BindHost: config.HTTPConfig.Address.Addr(), BindHost: config.HTTPConfig.Address.Addr(),
BindPort: int(config.HTTPConfig.Address.Port()), BindPort: int(config.HTTPConfig.Address.Port()),
@@ -489,13 +531,12 @@ func initWeb(opts options, clientBuildFS fs.FS, upd *updater.Updater) (web *webA
ReadHeaderTimeout: readHdrTimeout, ReadHeaderTimeout: readHdrTimeout,
WriteTimeout: writeTimeout, WriteTimeout: writeTimeout,
firstRun: Context.firstRun, clientFS: clientFS,
disableUpdate: disableUpdate,
runningAsService: opts.runningAsService, serveHTTP3: config.DNS.ServeHTTP3,
serveHTTP3: config.DNS.ServeHTTP3,
} }
web = newWebAPI(webConf) web = newWebAPI(&webConf)
if web == nil { if web == nil {
return nil, fmt.Errorf("initializing web: %w", err) return nil, fmt.Errorf("initializing web: %w", err)
} }
@@ -546,21 +587,9 @@ func run(opts options, clientBuildFS fs.FS) {
err = setupOpts(opts) err = setupOpts(opts)
fatalOnError(err) fatalOnError(err)
upd := updater.NewUpdater(&updater.Config{
Client: config.DNS.DnsfilterConf.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: Context.workDir,
ConfName: config.getConfigFilename(),
})
// TODO(e.burkov): This could be made earlier, probably as the option's // TODO(e.burkov): This could be made earlier, probably as the option's
// effect. // effect.
cmdlineUpdate(opts, upd) cmdlineUpdate(opts)
if !Context.firstRun { if !Context.firstRun {
// Save the updated config. // Save the updated config.
@@ -589,7 +618,7 @@ func run(opts options, clientBuildFS fs.FS) {
onConfigModified() onConfigModified()
} }
Context.web, err = initWeb(opts, clientBuildFS, upd) Context.web, err = initWeb(opts, clientBuildFS)
fatalOnError(err) fatalOnError(err)
if !Context.firstRun { if !Context.firstRun {
@@ -961,6 +990,62 @@ func detectFirstRun() bool {
return errors.Is(err, os.ErrNotExist) return errors.Is(err, os.ErrNotExist)
} }
// Connect to a remote server resolving hostname using our own DNS server.
//
// TODO(e.burkov): This messy logic should be decomposed and clarified.
//
// TODO(a.garipov): Support network.
func customDialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
log.Debug("home: customdial: dialing addr %q for network %s", addr, network)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
Timeout: time.Minute * 5,
}
if net.ParseIP(host) != nil || config.DNS.Port == 0 {
return dialer.DialContext(ctx, network, addr)
}
addrs, err := Context.dnsServer.Resolve(host)
if err != nil {
return nil, fmt.Errorf("resolving %q: %w", host, err)
}
log.Debug("dnsServer.Resolve: %q: %v", host, addrs)
if len(addrs) == 0 {
return nil, fmt.Errorf("couldn't lookup host: %q", host)
}
var dialErrs []error
for _, a := range addrs {
addr = net.JoinHostPort(a.String(), port)
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
dialErrs = append(dialErrs, err)
continue
}
return conn, err
}
return nil, errors.List(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
}
func getHTTPProxy(_ *http.Request) (*url.URL, error) {
if config.ProxyURL == "" {
return nil, nil
}
return url.Parse(config.ProxyURL)
}
// jsonError is a generic JSON error response. // jsonError is a generic JSON error response.
// //
// TODO(a.garipov): Merge together with the implementations in [dhcpd] and other // TODO(a.garipov): Merge together with the implementations in [dhcpd] and other
@@ -971,7 +1056,7 @@ type jsonError struct {
} }
// cmdlineUpdate updates current application and exits. // cmdlineUpdate updates current application and exits.
func cmdlineUpdate(opts options, upd *updater.Updater) { func cmdlineUpdate(opts options) {
if !opts.performUpdate { if !opts.performUpdate {
return return
} }
@@ -986,9 +1071,10 @@ func cmdlineUpdate(opts options, upd *updater.Updater) {
log.Info("cmdline update: performing update") log.Info("cmdline update: performing update")
info, err := upd.VersionInfo(true) updater := Context.updater
info, err := updater.VersionInfo(true)
if err != nil { if err != nil {
vcu := upd.VersionCheckURL() vcu := updater.VersionCheckURL()
log.Error("getting version info from %s: %s", vcu, err) log.Error("getting version info from %s: %s", vcu, err)
os.Exit(1) os.Exit(1)
@@ -1000,7 +1086,7 @@ func cmdlineUpdate(opts options, upd *updater.Updater) {
os.Exit(0) os.Exit(0)
} }
err = upd.Update(Context.firstRun) err = updater.Update(Context.firstRun)
fatalOnError(err) fatalOnError(err)
err = restartService() err = restartService()

View File

@@ -1,47 +0,0 @@
package home
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/url"
"time"
)
// httpClient returns a new HTTP client that uses the AdGuard Home's own DNS
// server for resolving hostnames. The resulting client should not be used
// until [Context.dnsServer] is initialized.
//
// TODO(a.garipov, e.burkov): This is rather messy. Refactor.
func httpClient() (c *http.Client) {
// Do not use Context.dnsServer.DialContext directly in the struct literal
// below, since Context.dnsServer may be nil when this function is called.
dialContext := func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
return Context.dnsServer.DialContext(ctx, network, addr)
}
return &http.Client{
// TODO(a.garipov): Make configurable.
Timeout: time.Minute * 5,
Transport: &http.Transport{
DialContext: dialContext,
Proxy: httpProxy,
TLSClientConfig: &tls.Config{
RootCAs: Context.tlsRoots,
CipherSuites: Context.tlsCipherIDs,
MinVersion: tls.VersionTLS12,
},
},
}
}
// httpProxy returns parses and returns an HTTP proxy URL from the config, if
// any.
func httpProxy(_ *http.Request) (u *url.URL, err error) {
if config.ProxyURL == "" {
return nil, nil
}
return url.Parse(config.ProxyURL)
}

View File

@@ -33,13 +33,9 @@ const (
// daemon. // daemon.
type program struct { type program struct {
clientBuildFS fs.FS clientBuildFS fs.FS
signals chan os.Signal
opts options opts options
} }
// type check
var _ service.Interface = (*program)(nil)
// Start implements service.Interface interface for *program. // Start implements service.Interface interface for *program.
func (p *program) Start(_ service.Service) (err error) { func (p *program) Start(_ service.Service) (err error) {
// Start should not block. Do the actual work async. // Start should not block. Do the actual work async.
@@ -52,14 +48,14 @@ func (p *program) Start(_ service.Service) (err error) {
} }
// Stop implements service.Interface interface for *program. // Stop implements service.Interface interface for *program.
func (p *program) Stop(_ service.Service) (err error) { func (p *program) Stop(_ service.Service) error {
select { // Stop should not block. Return with a few seconds.
case p.signals <- syscall.SIGINT: if Context.appSignalChannel == nil {
// Go on. os.Exit(0)
default:
// Stop should not block.
} }
Context.appSignalChannel <- syscall.SIGINT
return nil return nil
} }
@@ -198,7 +194,7 @@ func restartService() (err error) {
// - run: This is a special command that is not supposed to be used directly // - run: This is a special command that is not supposed to be used directly
// it is specified when we register a service, and it indicates to the app // it is specified when we register a service, and it indicates to the app
// that it is being run as a service/daemon. // that it is being run as a service/daemon.
func handleServiceControlAction(opts options, clientBuildFS fs.FS, signals chan os.Signal) { func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
// Call chooseSystem explicitly to introduce OpenBSD support for service // Call chooseSystem explicitly to introduce OpenBSD support for service
// package. It's a noop for other GOOS values. // package. It's a noop for other GOOS values.
chooseSystem() chooseSystem()
@@ -230,11 +226,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS, signals chan
} }
configureService(svcConfig) configureService(svcConfig)
s, err := service.New(&program{ s, err := service.New(&program{clientBuildFS: clientBuildFS, opts: runOpts}, svcConfig)
clientBuildFS: clientBuildFS,
signals: signals,
opts: runOpts,
}, svcConfig)
if err != nil { if err != nil {
log.Fatalf("service: initializing service: %s", err) log.Fatalf("service: initializing service: %s", err)
} }

View File

@@ -11,11 +11,11 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3" "github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
@@ -33,8 +33,6 @@ const (
) )
type webConfig struct { type webConfig struct {
updater *updater.Updater
clientFS fs.FS clientFS fs.FS
BindHost netip.Addr BindHost netip.Addr
@@ -54,13 +52,6 @@ type webConfig struct {
firstRun bool firstRun bool
// disableUpdate, if true, tells AdGuard Home to not check for updates.
disableUpdate bool
// runningAsService flag is set to true when options are passed from the
// service runner.
runningAsService bool
serveHTTP3 bool serveHTTP3 bool
} }
@@ -111,7 +102,7 @@ func newWebAPI(conf *webConfig) (w *webAPI) {
Context.mux.Handle("/install.html", preInstallHandler(clientFS)) Context.mux.Handle("/install.html", preInstallHandler(clientFS))
w.registerInstallHandlers() w.registerInstallHandlers()
} else { } else {
registerControlHandlers(w) registerControlHandlers()
} }
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock) w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
@@ -304,7 +295,7 @@ func (web *webAPI) mustStartHTTP3(address string) {
log.Debug("web: starting http/3 server") log.Debug("web: starting http/3 server")
err := web.httpsServer.server3.ListenAndServe() err := web.httpsServer.server3.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) { if !errors.Is(err, quic.ErrServerClosed) {
cleanupAlways() cleanupAlways()
log.Fatalf("web: http3: %s", err) log.Fatalf("web: http3: %s", err)
} }

View File

@@ -17,7 +17,7 @@ type Interface interface {
Process(ip netip.Addr) (host string, changed bool) Process(ip netip.Addr) (host string, changed bool)
} }
// Empty is an empty [Interface] implementation which does nothing. // Empty is an empty [Inteface] implementation which does nothing.
type Empty struct{} type Empty struct{}
// type check // type check

View File

@@ -5,13 +5,25 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/rdns" "github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// fakeRDNSExchanger is a mock [rdns.Exchanger] implementation for tests.
type fakeRDNSExchanger struct {
OnExchange func(ip netip.Addr) (host string, err error)
}
// type check
var _ rdns.Exchanger = (*fakeRDNSExchanger)(nil)
// Exchange implements [rdns.Exchanger] interface for *fakeRDNSExchanger.
func (e *fakeRDNSExchanger) Exchange(ip netip.Addr) (host string, err error) {
return e.OnExchange(ip)
}
func TestDefault_Process(t *testing.T) { func TestDefault_Process(t *testing.T) {
ip1 := netip.MustParseAddr("1.2.3.4") ip1 := netip.MustParseAddr("1.2.3.4")
revAddr1, err := netutil.IPToReversedAddr(ip1.AsSlice()) revAddr1, err := netutil.IPToReversedAddr(ip1.AsSlice())
@@ -69,7 +81,7 @@ func TestDefault_Process(t *testing.T) {
return "", nil return "", nil
} }
} }
exchanger := &aghtest.Exchanger{ exchanger := &fakeRDNSExchanger{
OnExchange: onExchange, OnExchange: onExchange,
} }

View File

@@ -9,9 +9,9 @@ require (
github.com/kisielk/errcheck v1.6.3 github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1 github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.16.0 github.com/securego/gosec/v2 v2.16.0
github.com/uudashr/gocognit v1.0.7 github.com/uudashr/gocognit v1.0.6
golang.org/x/tools v0.11.0 golang.org/x/tools v0.11.0
golang.org/x/vuln v1.0.0 golang.org/x/vuln v0.2.0
// TODO(a.garipov): Return to tagged releases once a new one appears. // TODO(a.garipov): Return to tagged releases once a new one appears.
honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee
mvdan.cc/gofumpt v0.5.0 mvdan.cc/gofumpt v0.5.0
@@ -27,7 +27,7 @@ require (
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/exp/typeparams v0.0.0-20230711023510-fffb14384f22 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect golang.org/x/sys v0.10.0 // indirect

View File

@@ -39,8 +39,8 @@ github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyO
github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo= github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y=
github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -52,8 +52,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1 h1:VXDua8UTGWl3e7L5kCk5Vyt0LA3QpsyRu6XXL7K3v1w= golang.org/x/exp/typeparams v0.0.0-20230711023510-fffb14384f22 h1:e8iSCQYXZ4EB6q3kIfy2fgPFTvDbozqzRe4OuIOyrL4=
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230711023510-fffb14384f22/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@@ -98,8 +98,8 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g= golang.org/x/vuln v0.2.0 h1:Dlz47lW0pvPHU7tnb10S8vbMn9GnV2B6eyT7Tem5XBI=
golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4= golang.org/x/vuln v0.2.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -48,8 +48,9 @@ func (Empty) Process(_ context.Context, _ netip.Addr) (info *Info, changed bool)
// Config is the configuration structure for Default. // Config is the configuration structure for Default.
type Config struct { type Config struct {
// DialContext is used to create TCP connections to WHOIS servers. // DialContext specifies the dial function for creating unencrypted TCP
DialContext DialContextFunc // connections.
DialContext func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// ServerAddr is the address of the WHOIS server. // ServerAddr is the address of the WHOIS server.
ServerAddr string ServerAddr string
@@ -77,13 +78,6 @@ type Config struct {
Port uint16 Port uint16
} }
// DialContextFunc is the semantic alias for dialing functions, such as
// [http.Transport.DialContext].
//
// TODO(a.garipov): Move to aghnet once it stops importing aghtest, because
// otherwise there is an import cycle.
type DialContextFunc = func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// Default is the default WHOIS information processor. // Default is the default WHOIS information processor.
type Default struct { type Default struct {
// cache is the cache containing IP addresses of clients. An active IP // cache is the cache containing IP addresses of clients. An active IP
@@ -92,8 +86,9 @@ type Default struct {
// resolve the same IP. // resolve the same IP.
cache gcache.Cache cache gcache.Cache
// dialContext is used to create TCP connections to WHOIS servers. // dialContext connects to a remote server resolving hostname using our own
dialContext DialContextFunc // DNS server and unecrypted TCP connection.
dialContext func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// serverAddr is the address of the WHOIS server. // serverAddr is the address of the WHOIS server.
serverAddr string serverAddr string
@@ -220,7 +215,7 @@ func (w *Default) query(ctx context.Context, target, serverAddr string) (data []
return nil, err return nil, err
} }
_ = conn.SetDeadline(time.Now().Add(w.timeout)) _ = conn.SetReadDeadline(time.Now().Add(w.timeout))
_, err = io.WriteString(conn, target+"\r\n") _, err = io.WriteString(conn, target+"\r\n")
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is. // Don't wrap the error since it's informative enough as is.
@@ -315,7 +310,7 @@ func (w *Default) requestInfo(
kv, err := w.queryAll(ctx, ip.String()) kv, err := w.queryAll(ctx, ip.String())
if err != nil { if err != nil {
log.Debug("whois: querying %q: %s", ip, err) log.Debug("whois: quering about %q: %s", ip, err)
return nil, true return nil, true
} }

View File

@@ -113,14 +113,20 @@ func TestDefault_Process(t *testing.T) {
return copy(b, tc.data), io.EOF return copy(b, tc.data), io.EOF
}, },
OnWrite: func(b []byte) (n int, err error) { return len(b), nil }, OnWrite: func(b []byte) (n int, err error) {
OnClose: func() (err error) { return nil }, return len(b), nil
OnSetDeadline: func(t time.Time) (err error) { return nil }, },
OnClose: func() (err error) {
return nil
},
OnSetReadDeadline: func(t time.Time) (err error) {
return nil
},
} }
w := whois.New(&whois.Config{ w := whois.New(&whois.Config{
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
DialContext: func(_ context.Context, _, _ string) (_ net.Conn, _ error) { DialContext: func(_ context.Context, _, addr string) (_ net.Conn, _ error) {
hit = 0 hit = 0
return fakeConn, nil return fakeConn, nil

View File

@@ -176,29 +176,16 @@ run_linter gocognit --over 10\
./internal/aghchan/\ ./internal/aghchan/\
./internal/aghhttp/\ ./internal/aghhttp/\
./internal/aghio/\ ./internal/aghio/\
./internal/client/\
./internal/dhcpsvc\
./internal/filtering/hashprefix/\ ./internal/filtering/hashprefix/\
./internal/filtering/rulelist/\ ./internal/filtering/rulelist/\
./internal/next/\ ./internal/next/\
./internal/rdns/\ ./internal/rdns/\
./internal/schedule/\
./internal/tools/\ ./internal/tools/\
./internal/version/\ ./internal/version/\
./internal/whois/\ ./internal/whois/\
./scripts/\ ./scripts/\
; ;
# TODO(a.garipov): move these to the group above.
run_linter gocognit --over 20 ./internal/aghnet/ ./internal/querylog/
run_linter gocognit --over 19 ./internal/dnsforward/ ./internal/home/
run_linter gocognit --over 18 ./internal/aghtls/
run_linter gocognit --over 17 ./internal/filtering ./internal/filtering/rewrite/
run_linter gocognit --over 15 ./internal/aghos/ ./internal/dhcpd/
run_linter gocognit --over 14 ./internal/stats/
run_linter gocognit --over 12 ./internal/updater/
run_linter gocognit --over 11 ./internal/aghtest/
run_linter ineffassign ./... run_linter ineffassign ./...
run_linter unparam ./... run_linter unparam ./...
@@ -224,14 +211,12 @@ run_linter gosec --quiet\
./internal/aghnet\ ./internal/aghnet\
./internal/aghos\ ./internal/aghos\
./internal/aghtest\ ./internal/aghtest\
./internal/client\
./internal/dhcpd\ ./internal/dhcpd\
./internal/dhcpsvc\ ./internal/dhcpsvc\
./internal/dnsforward\ ./internal/dnsforward\
./internal/filtering/hashprefix/\ ./internal/filtering/hashprefix/\
./internal/filtering/rulelist/\ ./internal/filtering/rulelist/\
./internal/next\ ./internal/next\
./internal/rdns\
./internal/schedule\ ./internal/schedule\
./internal/stats\ ./internal/stats\
./internal/tools\ ./internal/tools\