Compare commits

..

12 Commits

Author SHA1 Message Date
Dimitry Kolyshev
f6b704fb58 Merge remote-tracking branch 'origin/master' into 6263-custom-ups-cache 2023-11-24 09:55:04 +02:00
Dimitry Kolyshev
780d001cef all: imp locales 2023-11-24 09:54:42 +02:00
Dimitry Kolyshev
820bcf0e23 Merge remote-tracking branch 'origin/master' into 6263-custom-ups-cache
# Conflicts:
#	CHANGELOG.md
2023-11-23 11:16:54 +02:00
Dimitry Kolyshev
e7ca515630 Merge remote-tracking branch 'origin/master' into 6263-custom-ups-cache 2023-11-22 09:49:32 +02:00
Dimitry Kolyshev
23392d08b1 Merge remote-tracking branch 'origin/6263-custom-ups-cache' into 6263-custom-ups-cache 2023-11-22 09:49:10 +02:00
Dimitry Kolyshev
a09cd190c0 home: imp code 2023-11-22 09:48:43 +02:00
Ildar Kamalov
feb15745d2 client: move cache fields to the upstream tab 2023-11-21 17:27:38 +03:00
Ildar Kamalov
789060e4d4 client: add form fields 2023-11-21 16:28:03 +03:00
Dimitry Kolyshev
a700bfb42e Merge remote-tracking branch 'origin/master' into 6263-custom-ups-cache 2023-11-21 11:44:14 +02:00
Dimitry Kolyshev
7669419b59 home: imp code 2023-11-21 11:34:17 +02:00
Dimitry Kolyshev
4a9dc1d6c1 docs: http client docs 2023-11-20 15:10:38 +02:00
Dimitry Kolyshev
944e0d7a4b home: http client dns cache 2023-11-20 15:10:38 +02:00
75 changed files with 1461 additions and 1453 deletions

View File

@@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.20.12'
'GO_VERSION': '1.20.11'
'NODE_VERSION': '16'
'on':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.20.12'
'GO_VERSION': '1.20.11'
'on':
'push':

View File

@@ -14,36 +14,20 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.43] - 2023-12-20 (APPROX.)
## [v0.107.42] - 2023-12-06 (APPROX.)
See also the [v0.107.43 GitHub milestone][ms-v0.107.43].
See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
[ms-v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/milestone/78?closed=1
[ms-v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/milestone/77?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.42] - 2023-12-07
See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-39326, CVE-2023-45283, and CVE-2023-45285 Go vulnerabilities fixed in
[Go 1.20.12][go-1.20.12].
### Added
- Ability to set client's custom DNS cache ([#6263]).
- Ability to set client's custom DNS cache ([#6362], [dnsproxy#169]).
- Ability to disable plain-DNS serving through configuration file if an
encrypted protocol is already enabled ([#1660]).
encrypted protocol is already used ([#1660]).
- Ability to specify rate limiting settings in the Web UI ([#6369]).
### Changed
@@ -65,13 +49,16 @@ See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
[#1660]: https://github.com/AdguardTeam/AdGuardHome/issues/1660
[#5759]: https://github.com/AdguardTeam/AdGuardHome/issues/5759
[#6263]: https://github.com/AdguardTeam/AdGuardHome/issues/6263
[#6362]: https://github.com/AdguardTeam/AdGuardHome/issues/6362
[#6369]: https://github.com/AdguardTeam/AdGuardHome/issues/6369
[#6402]: https://github.com/AdguardTeam/AdGuardHome/issues/6402
[#6420]: https://github.com/AdguardTeam/AdGuardHome/issues/6420
[go-1.20.12]: https://groups.google.com/g/golang-announce/c/iLGK3x6yuNo/m/z6MJ-eB0AQAJ
[ms-v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/milestone/77?closed=1
[dnsproxy#169] https://github.com/AdguardTeam/dnsproxy/issues/169
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
@@ -2658,12 +2645,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...HEAD
[v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...v0.107.43
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...HEAD
[v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...v0.107.42
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...HEAD
[v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...v0.107.41
[v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...v0.107.40
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39

View File

@@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
'stages':
- 'Build frontend':
@@ -272,7 +272,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -287,4 +287,4 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'

View File

@@ -10,7 +10,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
'snapcraftChannel': 'edge'
'stages':
@@ -191,7 +191,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
'snapcraftChannel': 'beta'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
@@ -207,5 +207,5 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
'snapcraftChannel': 'candidate'

View File

@@ -5,7 +5,7 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:7.5'
'stages':
- 'Tests':

View File

@@ -1,7 +1,6 @@
{
"client_settings": "Налады кліентаў",
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
"example_upstream_comment": "каментар.",
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
"parallel_requests": "Паралельныя запыты",
@@ -144,8 +143,6 @@
"enforced_save_search": "Ужыты бяспечны пошук",
"number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук",
"average_processing_time": "Сярэдні час апрацоўкі запыту",
"average_upstream_response_time": "Сярэдні час водгуку upstream-сервера",
"response_time": "Час водгуку",
"average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах",
"block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў",
"filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «<a>Фільтрах</a>».",
@@ -310,15 +307,6 @@
"edns_use_custom_ip": "Выкарыстоўваць указаны IP для DNS",
"edns_use_custom_ip_desc": "Дазволіць выкарыстоўваць уласны IP для DNS",
"rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)",
"rate_limit_subnet_len_ipv4": "Даўжыня прэфікса падсеткі для адрасоў IPv4",
"rate_limit_subnet_len_ipv4_desc": "Даўжыня прэфікса падсеткі для адрасоў IPv4, якія выкарыстоўваюцца для абмежавання хуткасці. Значэнне па змаўчанні 24",
"rate_limit_subnet_len_ipv4_error": "Даўжыня прэфікса падсеткі IPv4 павінна быць ад 0 да 32",
"rate_limit_subnet_len_ipv6": "Даўжыня прэфікса падсеткі для адрасоў IPv6",
"rate_limit_subnet_len_ipv6_desc": "Даўжыня прэфікса падсеткі для адрасоў IPv6, якія выкарыстоўваюцца для абмежавання хуткасці. Значэнне па змаўчанні 56",
"rate_limit_subnet_len_ipv6_error": "Даўжыня прэфікса падсеткі IPv6 павінна быць ад 0 да 128",
"form_enter_rate_limit_subnet_len": "Увядзіце даўжыню прэфікса падсеткі для абмежавання хуткасці",
"rate_limit_whitelist": "Белы спіс з абмежаваннем хуткасці",
"rate_limit_whitelist_desc": "IP-адрасы выключаны з абмежавання хуткасці",
"rate_limit_whitelist_placeholder": "Увядзіце па адным адрасе на радок",
"blocking_ipv4_desc": "IP-адрас, што вяртаецца пры блакаванню A-запыту",
"blocking_ipv6_desc": "IP-адрас, што вяртаецца пры блакаванню AAAA-запыту",
@@ -734,8 +722,5 @@
"wednesday_short": "Ср.",
"thursday_short": "Чц.",
"friday_short": "Пт.",
"saturday_short": "Сб.",
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
"dns_cache_size": "Памер кэша DNS, у байтах"
"saturday_short": "Сб."
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Středa",
"thursday_short": "Čtvrtek",
"friday_short": "Pátek",
"saturday_short": "Sobota",
"upstream_dns_cache_configuration": "Konfigurace mezipaměti odchozího DNS",
"enable_upstream_dns_cache": "Povolit ukládání do mezipaměti DNS pro vlastní konfiguraci odchozího připojení tohoto klienta",
"dns_cache_size": "Velikost mezipaměti DNS v bajtech"
"saturday_short": "Sobota"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Ons",
"thursday_short": "Tors",
"friday_short": "Fre",
"saturday_short": "Lør",
"upstream_dns_cache_configuration": "Upstream DNS-cacheopsætning",
"enable_upstream_dns_cache": "Aktivér DNS-cachelagring for denne klients tilpassede upstream-opsætning",
"dns_cache_size": "DNS-cachestørrelse i bytes"
"saturday_short": "Lør"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Mi",
"thursday_short": "Do",
"friday_short": "Fr",
"saturday_short": "Sa",
"upstream_dns_cache_configuration": "Konfiguration des Upstream-DNS-Cache",
"enable_upstream_dns_cache": "Caching für die benutzerdefinierte Upstream-Server-Konfiguration dieses Clients aktivieren",
"dns_cache_size": "Größe des DNS-Cache, in Bytes"
"saturday_short": "Sa"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Mié.",
"thursday_short": "Jue.",
"friday_short": "Vie.",
"saturday_short": "Sáb.",
"upstream_dns_cache_configuration": "Configuración de la caché DNS upstream",
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché de DNS para la configuración personalizada de este cliente",
"dns_cache_size": "Tamaño de la caché DNS, en bytes"
"saturday_short": "Sáb."
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Ke",
"thursday_short": "To",
"friday_short": "Pe",
"saturday_short": "La",
"upstream_dns_cache_configuration": "Ylävirran DNS-välimuistin määritykset",
"enable_upstream_dns_cache": "Käytä DNS-välimuistia tämän päätelaitteen mukautetuissa ylävirtamäärityksissä",
"dns_cache_size": "DNS-välimuistin koko tavuina"
"saturday_short": "La"
}

View File

@@ -310,15 +310,6 @@
"edns_use_custom_ip": "Utiliser une IP personnalisée pour EDNS",
"edns_use_custom_ip_desc": "Autoriser l'utilisation d'une adresse IP personnalisée pour EDNS",
"rate_limit_desc": "Le nombre de requêtes par seconde quun seul client est autorisé à faire. Le réglage 0 fait illimité.",
"rate_limit_subnet_len_ipv4": "Longueur du préfixe de sous-réseau pour les adresses IPv4",
"rate_limit_subnet_len_ipv4_desc": "Longueur du préfixe de sous-réseau pour les adresses IPv4 utilisé pour la limitation de vitesse. La valeur par défaut est 24",
"rate_limit_subnet_len_ipv4_error": "La longueur du préfixe du sous-réseau IPv4 doit être entre 0 et 32",
"rate_limit_subnet_len_ipv6": "Longueur du préfixe de sous-réseau pour les adresses IPv6",
"rate_limit_subnet_len_ipv6_desc": "Longueur du préfixe de sous-réseau pour les adresses IPv6 utilisé pour la limitation de débit. La valeur par défaut est 56",
"rate_limit_subnet_len_ipv6_error": "La longueur du préfixe du sous-réseau IPv6 doit être entre 0 et 128",
"form_enter_rate_limit_subnet_len": "Saisissez la longueur du préfixe de sous-réseau pour la limitation de débit",
"rate_limit_whitelist": "Liste d'autorisation de limitation de débit",
"rate_limit_whitelist_desc": "Adresses IP exclues de la limitation du débit",
"rate_limit_whitelist_placeholder": "Saisissez une adresse IP par ligne",
"blocking_ipv4_desc": "Adresse IP à renvoyer pour une demande A bloquée",
"blocking_ipv6_desc": "Adresse IP à renvoyer pour une demande AAAA bloquée",
@@ -734,8 +725,5 @@
"wednesday_short": "Mer.",
"thursday_short": "Jeu.",
"friday_short": "Ven.",
"saturday_short": "Sam.",
"upstream_dns_cache_configuration": "Configuration du cache DNS en amont",
"enable_upstream_dns_cache": "Activer la mise en cache pour la configuration personnalisée du serveur en amont de ce client",
"dns_cache_size": "Taille du cache DNS, en bytes"
"saturday_short": "Sam."
}

View File

@@ -310,15 +310,6 @@
"edns_use_custom_ip": "Koristi prilagođeni IP za EDNS",
"edns_use_custom_ip_desc": "Dopusti korištenje prilagođenog IP-a za EDNS",
"rate_limit_desc": "Broj zahtjeva u sekundi koji su dopušteni po jednom klijentu. Postavljanje na 0 znači neograničeno.",
"rate_limit_subnet_len_ipv4": "Duljina prefiksa podmreže za IPv4 adrese",
"rate_limit_subnet_len_ipv4_desc": "Duljina prefiksa podmreže za IPv4 adrese koje se koriste za ograničavanje brzine. Zadana vrijednost je 24",
"rate_limit_subnet_len_ipv4_error": "Dužina IPv4 prefiksa podmreže trebala bi biti između 0 i 32",
"rate_limit_subnet_len_ipv6": "Duljina prefiksa podmreže za IPv6 adrese",
"rate_limit_subnet_len_ipv6_desc": "Duljina prefiksa podmreže za IPv6 adrese koje se koriste za ograničavanje brzine. Zadana vrijednost je 56",
"rate_limit_subnet_len_ipv6_error": "Dužina IPv6 prefiksa podmreže trebala bi biti između 0 i 128",
"form_enter_rate_limit_subnet_len": "Unesite duljinu prefiksa podmreže za ograničenje brzine",
"rate_limit_whitelist": "Popis dopuštenih za ograničavanje brzine",
"rate_limit_whitelist_desc": "IP adrese isključene iz ograničenja brzine",
"rate_limit_whitelist_placeholder": "Unesite jednu adresu poslužitelja po retku",
"blocking_ipv4_desc": "Povratna IP adresa za blokirane A zahtjeve",
"blocking_ipv6_desc": "Povratna IP adresa za blokirane AAAA zahtjeve",
@@ -734,8 +725,5 @@
"wednesday_short": "Sri",
"thursday_short": "Čet",
"friday_short": "Pet",
"saturday_short": "Sub",
"upstream_dns_cache_configuration": "Konfiguracija predmemoriranja upstream DNS poslužitelja",
"enable_upstream_dns_cache": "Uključite keširanje za korisničku konfiguraciju upstream servera ovog klijenta",
"dns_cache_size": "Veličina DNS predmemorije, u bajtovima"
"saturday_short": "Sub"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Szer",
"thursday_short": "Csüt",
"friday_short": "Pén",
"saturday_short": "Szom",
"upstream_dns_cache_configuration": "Upstream DNS gyorsítótár konfigurációja",
"enable_upstream_dns_cache": "A DNS gyorsítótárazásának engedélyezése az ügyfél egyéni upstream konfigurációjához",
"dns_cache_size": "DNS gyorsítótár mérete, bájtokban"
"saturday_short": "Szom"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Rab",
"thursday_short": "Kam",
"friday_short": "Jum",
"saturday_short": "Sab",
"upstream_dns_cache_configuration": "Konfigurasi cache DNS upstream",
"enable_upstream_dns_cache": "Aktifkan cache DNS untuk konfigurasi upstream kustom klien ini",
"dns_cache_size": "Ukuran cache DNS, dalam byte"
"saturday_short": "Sab"
}

View File

@@ -310,15 +310,6 @@
"edns_use_custom_ip": "Usa IP personalizzato per EDNS",
"edns_use_custom_ip_desc": "Consentire l'uso di un IP personalizzato per EDNS",
"rate_limit_desc": "Il numero di richieste al secondo consentite da un singolo client. Impostare questo valore a 0 rimuove le limitazioni.",
"rate_limit_subnet_len_ipv4": "Lunghezza prefisso di sottorete per indirizzi IPv4",
"rate_limit_subnet_len_ipv4_desc": "Lunghezza prefisso sottorete per indirizzi IPv4 usati per la limitazione della velocità. Valore predefinito 24",
"rate_limit_subnet_len_ipv4_error": "La lunghezza del prefisso di sottorete IPv4 deve essere compresa tra 0 e 32",
"rate_limit_subnet_len_ipv6": "Lunghezza prefisso di sottorete per indirizzi IPv6",
"rate_limit_subnet_len_ipv6_desc": "Lunghezza prefisso di sottorete per indirizzi IPv6 usati per la limitazione della velocità. Valore predefinito 56",
"rate_limit_subnet_len_ipv6_error": "La lunghezza del prefisso di sottorete IPv6 deve essere compresa tra 0 e 128",
"form_enter_rate_limit_subnet_len": "Inserisci lunghezza prefisso di sottorete per limitazione velocità",
"rate_limit_whitelist": "Lista consentita per limitazione velocità",
"rate_limit_whitelist_desc": "Indirizzi IP esclusi dalla limitazione della velocità",
"rate_limit_whitelist_placeholder": "Inserisci un indirizzo IP per riga",
"blocking_ipv4_desc": "Indirizzo IP per una richiesta DNS IPv4 bloccata",
"blocking_ipv6_desc": "Indirizzo IP restituito per una richiesta DNS IPv6 bloccata",
@@ -734,8 +725,5 @@
"wednesday_short": "Mer",
"thursday_short": "Gio",
"friday_short": "Ven",
"saturday_short": "Sab",
"upstream_dns_cache_configuration": "Configurazione cache DNS upstream",
"enable_upstream_dns_cache": "Abilita cache DNS per la configurazione upstream personalizzata del client",
"dns_cache_size": "Dimensioni cache DNS (in byte)"
"saturday_short": "Sab"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "水",
"thursday_short": "木",
"friday_short": "金",
"saturday_short": "土",
"upstream_dns_cache_configuration": "Upstream DNS cache configurationアップストリームDNSキャッシュの構成",
"enable_upstream_dns_cache": "このクライアントのカスタムアップストリーム構成に対してDNSキャッシュを有効にする",
"dns_cache_size": "DNSキャッシュサイズバイト単位"
"saturday_short": "土"
}

View File

@@ -310,15 +310,6 @@
"edns_use_custom_ip": "EDNS에 사용자 지정 IP 사용",
"edns_use_custom_ip_desc": "EDNS에 사용자 지정 IP 사용하도록 허용합니다.",
"rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)",
"rate_limit_subnet_len_ipv4": "IPv4 주소의 서브넷 접두사 길이",
"rate_limit_subnet_len_ipv4_desc": "속도 제한에 사용되는 IPv4 주소의 서브넷 접두사 길이입니다. 기본값은 24입니다.",
"rate_limit_subnet_len_ipv4_error": "IPv4 서브넷 접두사 길이는 0에서 32 사이여야 합니다.",
"rate_limit_subnet_len_ipv6": "IPv6 주소의 서브넷 접두사 길이",
"rate_limit_subnet_len_ipv6_desc": "속도 제한에 사용되는 IPv6 주소의 서브넷 접두사 길이입니다. 기본값은 56입니다.",
"rate_limit_subnet_len_ipv6_error": "IPv6 서브넷 접두사 길이는 0에서 128 사이여야 합니다.",
"form_enter_rate_limit_subnet_len": "속도 제한을 위한 서브넷 접두사 길이를 입력하세요",
"rate_limit_whitelist": "속도 제한 허용 목록",
"rate_limit_whitelist_desc": "속도 제한에서 제외되는 IP 주소",
"rate_limit_whitelist_placeholder": "한 줄에 하나씩 IP 주소를 입력하세요.",
"blocking_ipv4_desc": "차단된 A 요청에 대해서 반환할 IP 주소",
"blocking_ipv6_desc": "차단된 AAAA 요청에 대해서 반환할 IP 주소",
@@ -734,8 +725,5 @@
"wednesday_short": "수",
"thursday_short": "목",
"friday_short": "금",
"saturday_short": "토",
"upstream_dns_cache_configuration": "업스트림 DNS 캐시 설정",
"enable_upstream_dns_cache": "이 클라이언트의 사용자 지정 업스트림 설정에서 DNS 캐싱 사용",
"dns_cache_size": "DNS 캐시 크기(바이트)"
"saturday_short": "토"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "wo",
"thursday_short": "do",
"friday_short": "vr",
"saturday_short": "za",
"upstream_dns_cache_configuration": "Upstream DNS-cacheconfiguratie",
"enable_upstream_dns_cache": "DNS-caching inschakelen voor de aangepaste upstream-configuratie van deze client",
"dns_cache_size": "DNS-cachegrootte, in bytes"
"saturday_short": "za"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Śro",
"thursday_short": "Czw",
"friday_short": "Pt",
"saturday_short": "Sob",
"upstream_dns_cache_configuration": "Konfiguracja pamięci podręcznej upstream serwerów DNS",
"enable_upstream_dns_cache": "Włącz pamięć podręczną dla niestandardowej konfiguracji serwera upstream tego klienta",
"dns_cache_size": "Rozmiar pamięci podręcznej DNS, w bajtach"
"saturday_short": "Sob"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Quar",
"thursday_short": "Qui",
"friday_short": "Sex",
"saturday_short": "Sab",
"upstream_dns_cache_configuration": "Configuração do cache de DNS upstream",
"enable_upstream_dns_cache": "Ativar o armazenamento em cache do DNS para a configuração de upstream personalizada deste cliente",
"dns_cache_size": "Tamanho do cache do DNS, em bytes"
"saturday_short": "Sab"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Quarta",
"thursday_short": "Quinta",
"friday_short": "Sexta",
"saturday_short": "Sábado",
"upstream_dns_cache_configuration": "Configuração da cache do DNS upstream",
"enable_upstream_dns_cache": "Ativar o armazenamento em cache do DNS para a configuração de upstream personalizada deste cliente",
"dns_cache_size": "Tamanho da cache DNS, em bytes"
"saturday_short": "Sábado"
}

View File

@@ -310,15 +310,6 @@
"edns_use_custom_ip": "Utilizați IP personalizat pentru EDNS",
"edns_use_custom_ip_desc": "Permiteți utilizarea IP-ului personalizat pentru EDNS",
"rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.",
"rate_limit_subnet_len_ipv4": "Lungimea prefixului de subrețea pentru adrese IPv4",
"rate_limit_subnet_len_ipv4_desc": "Lungimea prefixului de subrețea pentru adresele IPv4 utilizate pentru limitarea ratei. Valoarea implicită este 24",
"rate_limit_subnet_len_ipv4_error": "Lungimea prefixului de subrețea IPv4 ar trebui să fie între 0 și 32",
"rate_limit_subnet_len_ipv6": "Lungimea prefixului de subrețea pentru adrese IPv6",
"rate_limit_subnet_len_ipv6_desc": "Lungimea prefixului de subrețea pentru adresele IPv6 utilizate pentru limitarea ratei. Valoarea implicită este 56",
"rate_limit_subnet_len_ipv6_error": "Lungimea prefixului de subrețea IPv6 ar trebui să fie între 0 și 128",
"form_enter_rate_limit_subnet_len": "Introduceți lungimea prefixului de subrețea pentru limitarea ratei",
"rate_limit_whitelist": "Lista permisă pentru limitarea ratei",
"rate_limit_whitelist_desc": "Adresele IP excluse de la limitarea ratei",
"rate_limit_whitelist_placeholder": "Introduceți o adresă IP per linie",
"blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare",
"blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare",
@@ -734,8 +725,5 @@
"wednesday_short": "mi",
"thursday_short": "jo",
"friday_short": "vi",
"saturday_short": "sa",
"upstream_dns_cache_configuration": "Configurarea cache-ului DNS în amonte",
"enable_upstream_dns_cache": "Activați memoria cache DNS pentru configurația personalizată în amonte a acestui client",
"dns_cache_size": "Dimensiunea cache-ului DNS, în octeți"
"saturday_short": "sa"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Ср",
"thursday_short": "Чт",
"friday_short": "Пт",
"saturday_short": "Сб",
"upstream_dns_cache_configuration": "Конфигурация кеша upstream DNS-серверов",
"enable_upstream_dns_cache": "Включить кеширование для пользовательской конфигурации upstream-серверов этого клиента",
"dns_cache_size": "Размер DNS-кеша в байтах"
"saturday_short": "Сб"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Str",
"thursday_short": "Štr",
"friday_short": "Pia",
"saturday_short": "Sob",
"upstream_dns_cache_configuration": "Konfigurácia cache pamäte DNS pre upstream",
"enable_upstream_dns_cache": "Zapnúť ukladanie DNS do cache pamäte pre vlastnú konfiguráciu odosielania tohto klienta",
"dns_cache_size": "Veľkosť cache pamäte DNS v bajtoch"
"saturday_short": "Sob"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Sre",
"thursday_short": "Čet",
"friday_short": "Pet",
"saturday_short": "Sob",
"upstream_dns_cache_configuration": "Nastavitve predpomnilnika gorvodnega DNS",
"enable_upstream_dns_cache": "Omogoči predpomnjenje nastavitev gorvodnega DNS po meri tega odjemalca",
"dns_cache_size": "Velikost predpomnilnika DNS, v bajtih"
"saturday_short": "Sob"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Sre",
"thursday_short": "Čet",
"friday_short": "Pet",
"saturday_short": "Sub",
"upstream_dns_cache_configuration": "Konfiguracija keša upstream DNS servera",
"enable_upstream_dns_cache": "Uključite keširanje za korisničku konfiguraciju upstream servera ovog klijenta",
"dns_cache_size": "Veličina DNS keša, u bajtovima"
"saturday_short": "Sub"
}

View File

@@ -310,14 +310,6 @@
"edns_use_custom_ip": "Använd anpassad IP för EDNS",
"edns_use_custom_ip_desc": "Tillåt att använda anpassad IP för EDNS",
"rate_limit_desc": "Antalet förfrågningar per sekund som tillåts per klient. Att sätta den till 0 innebär ingen gräns.",
"rate_limit_subnet_len_ipv4": "Prefixlängd för subnät för IPv4-adresser",
"rate_limit_subnet_len_ipv4_desc": "Subnätprefixlängd för IPv4-adresser som används för hastighetsbegränsning. Standard är 24",
"rate_limit_subnet_len_ipv4_error": "IPv4-subnätets prefixlängd ska vara mellan 0 och 32",
"rate_limit_subnet_len_ipv6": "Prefixlängd för subnät för IPv6-adresser",
"rate_limit_subnet_len_ipv6_desc": "Subnätprefixlängd för IPv6-adresser som används för hastighetsbegränsning. Standard är 56",
"rate_limit_subnet_len_ipv6_error": "IPv6-subnätets prefixlängd ska vara mellan 0 och 128",
"form_enter_rate_limit_subnet_len": "Ange subnätprefixlängd för hastighetsbegränsning",
"rate_limit_whitelist_desc": "IP-adresser uteslutna från hastighetsbegränsning",
"rate_limit_whitelist_placeholder": "Ange en IP-adress per rad",
"blocking_ipv4_desc": "IP adress som ska returneras för en blockerad A förfrågan",
"blocking_ipv6_desc": "IP adress som ska returneras för en blockerad AAAA förfrågan",
@@ -733,8 +725,5 @@
"wednesday_short": "Ons",
"thursday_short": "Tor",
"friday_short": "Fre",
"saturday_short": "Lör",
"upstream_dns_cache_configuration": "Konfiguration av uppströms DNS-cache",
"enable_upstream_dns_cache": "Aktivera DNS-cachelagring för den här klientens anpassade uppströmskonfiguration",
"dns_cache_size": "DNS-cachestorlek, i byte"
"saturday_short": "Lör"
}

View File

@@ -322,7 +322,7 @@
"rate_limit_whitelist_placeholder": "Her satıra bir IP adresi girin",
"blocking_ipv4_desc": "Engellenen bir A isteği için geri döndürülecek IP adresi",
"blocking_ipv6_desc": "Engellenen bir AAAA isteği için geri döndürülecek IP adresi",
"blocking_mode_default": "Varsayılan: Reklam engelleme stili kuralı tarafından engellendiğinde sıfır IP adresiyle (A için 0.0.0.0; :: AAAA için) yanıt verin; /etc/hosts-tarzı kural tarafından engellendiğinde, kuralda belirtilen IP adresiyle yanıt verin",
"blocking_mode_default": "Varsayılan: Reklam engelleme tarzı kural tarafından engellendiğinde sıfır IP adresiyle (A için 0.0.0.0; :: AAAA için) yanıt verin; /etc/hosts-tarzı kural tarafından engellendiğinde, kuralda belirtilen IP adresiyle yanıt verin",
"blocking_mode_refused": "REFUSED: REFUSED koduyla yanıt verin",
"blocking_mode_nxdomain": "NXDOMAIN: NXDOMAIN koduyla yanıt verin",
"blocking_mode_null_ip": "Boş IP: Sıfır IP adresiyle yanıt verin (A için 0.0.0.0; :: AAAA için)",
@@ -734,8 +734,5 @@
"wednesday_short": "Çar",
"thursday_short": "Per",
"friday_short": "Cum",
"saturday_short": "Cmt",
"upstream_dns_cache_configuration": "Üst kaynak DNS önbellek yapılandırması",
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğe almayı etkinleştir",
"dns_cache_size": "DNS önbellek boyutu, bayt cinsinden"
"saturday_short": "Cmt"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "СР",
"thursday_short": "ЧТ",
"friday_short": "ПТ",
"saturday_short": "СБ",
"upstream_dns_cache_configuration": "Конфігурація кешу upstream DNS-серверів",
"enable_upstream_dns_cache": "Увімкнути кешування для користувацької конфігурації upstream-серверів цього клієнта",
"dns_cache_size": "Розмір кешу DNS, у байтах"
"saturday_short": "СБ"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "Thứ 4",
"thursday_short": "Thứ 5",
"friday_short": "Thứ 6",
"saturday_short": "Thứ 7",
"upstream_dns_cache_configuration": "Cấu hình bộ nhớ đệm upstream của các máy chủ DNS",
"enable_upstream_dns_cache": "Bật bộ nhớ cache cho cấu hình ngược dòng của máy chủ upstream của khách hàng này",
"dns_cache_size": "Kích thước bộ nhớ cache DNS, tính bằng byte"
"saturday_short": "Thứ 7"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "周三",
"thursday_short": "周四",
"friday_short": "周五",
"saturday_short": "周六",
"upstream_dns_cache_configuration": "上游 DNS 缓存配置",
"enable_upstream_dns_cache": "为该客户端的自定义上游配置启用 DNS 缓存",
"dns_cache_size": "DNS 缓存大小,单位:字节"
"saturday_short": "周六"
}

View File

@@ -734,8 +734,5 @@
"wednesday_short": "週三",
"thursday_short": "週四",
"friday_short": "週五",
"saturday_short": "週六",
"upstream_dns_cache_configuration": "上游 DNS 快取設定",
"enable_upstream_dns_cache": "啟用本用戶端自訂上游配置的 DNS 快取",
"dns_cache_size": "DNS 快取大小,單位:位元"
"saturday_short": "週六"
}

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2023-12-01T15:24:07.522Z",
"timeUpdated": "2023-11-10T12:55:56.663Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -640,8 +640,7 @@
"name": "AdChina",
"categoryId": 4,
"url": "http://www.adchina.com/",
"companyId": null,
"source": "AdGuard"
"companyId": "alibaba"
},
"adcito": {
"name": "Adcito",
@@ -1322,13 +1321,6 @@
"companyId": "adobe",
"source": "AdGuard"
},
"adobe_experience_league": {
"name": "Adobe Experience League",
"categoryId": 6,
"url": "https://experienceleague.adobe.com/",
"companyId": "adobe",
"source": "AdGuard"
},
"adobe_login": {
"name": "Adobe Login",
"categoryId": 2,
@@ -2289,29 +2281,27 @@
"name": "Alibaba",
"categoryId": 8,
"url": "http://www.alibaba.com/",
"companyId": "softbank",
"source": "AdGuard"
"companyId": "alibaba"
},
"alibaba_cloud": {
"name": "Alibaba Cloud",
"categoryId": 10,
"url": "https://www.alibabacloud.com/",
"companyId": "softbank",
"companyId": "alibaba",
"source": "AdGuard"
},
"alibaba_ucbrowser": {
"name": "UC Browser",
"categoryId": 8,
"url": "https://ucweb.com/",
"companyId": "softbank",
"companyId": "alibaba",
"source": "AdGuard"
},
"alipay.com": {
"name": "Alipay",
"categoryId": 2,
"url": "https://global.alipay.com/",
"companyId": "softbank",
"source": "AdGuard"
"url": "https://www.alipay.com/",
"companyId": "alibaba"
},
"alivechat": {
"name": "AliveChat",
@@ -3777,11 +3767,10 @@
"companyId": "branica"
},
"braze": {
"name": "Braze, Inc.",
"name": "Braze",
"categoryId": 6,
"url": "https://www.braze.com/",
"companyId": "braze",
"source": "AdGuard"
"companyId": "braze_inc"
},
"brealtime": {
"name": "EMX Digital",
@@ -12545,7 +12534,7 @@
"name": "Network Time Protocol",
"categoryId": 5,
"url": "https://ntp.org/",
"companyId": "network_time_foundation",
"companyId": "ntppool",
"source": "AdGuard"
},
"nttcom_online_marketing_solutions": {
@@ -17073,8 +17062,7 @@
"name": "Taobao",
"categoryId": 4,
"url": "https://world.taobao.com/",
"companyId": "softbank",
"source": "AdGuard"
"companyId": "alibaba"
},
"tapad": {
"name": "Tapad",
@@ -20443,7 +20431,6 @@
"nedstat.com": "adobe_experience_cloud",
"omtrdc.net": "adobe_experience_cloud",
"sitestat.com": "adobe_experience_cloud",
"adobedc.net": "adobe_experience_league",
"adobelogin.com": "adobe_login",
"adobetag.com": "adobe_tagmanager",
"typekit.com": "adobe_typekit",
@@ -21059,7 +21046,6 @@
"brandwire.tv": "brandwire.tv",
"branica.com": "branica",
"appboycdn.com": "braze",
"braze.com": "braze",
"brealtime.com": "brealtime",
"bridgetrack.com": "bridgetrack",
"brightcove.com": "brightcove",
@@ -23154,11 +23140,8 @@
"s-microsoft.com": "microsoft",
"trouter.io": "microsoft",
"windows.net": "microsoft",
"aka.ms": "microsoft",
"microsoftazuread-sso.com": "microsoft",
"bingapis.com": "microsoft",
"msauth.net": "microsoft",
"msauthimages.net": "microsoft",
"msftauth.net": "microsoft",
"msftstatic.com": "microsoft",
"msidentity.com": "microsoft",
@@ -23249,13 +23232,10 @@
"mrpdata.net": "mrpdata",
"mrskincash.com": "mrskincash",
"a-msedge.net": "msedge",
"b-msedge.net": "msedge",
"e-msedge.net": "msedge",
"k-msedge.net": "msedge",
"l-msedge.net": "msedge",
"s-msedge.net": "msedge",
"t-msedge.net": "msedge",
"wac-msedge.net": "msedge",
"msn.com": "msn",
"s-msn.com": "msn",
"musculahq.appspot.com": "muscula",

26
go.mod
View File

@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
go 1.20
require (
github.com/AdguardTeam/dnsproxy v0.60.0
github.com/AdguardTeam/golibs v0.18.0
github.com/AdguardTeam/dnsproxy v0.59.1
github.com/AdguardTeam/golibs v0.17.2
github.com/AdguardTeam/urlfilter v0.17.3
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
@@ -17,7 +17,7 @@ require (
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.4.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@@ -26,18 +26,18 @@ require (
// TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.57
github.com/miekg/dns v1.1.56
github.com/quic-go/quic-go v0.40.0
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.1
go.etcd.io/bbolt v1.3.8
golang.org/x/crypto v0.16.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/net v0.19.0
golang.org/x/sys v0.15.0
golang.org/x/crypto v0.15.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/net v0.18.0
golang.org/x/sys v0.14.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
howett.net/plist v1.0.0
)
require (
@@ -47,12 +47,12 @@ require (
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
// TODO(a.garipov): Upgrade to v0.5.0 once we switch to Go 1.21+.
github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/onsi/ginkgo/v2 v2.13.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.19 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
@@ -62,5 +62,5 @@ require (
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
golang.org/x/tools v0.15.0 // indirect
)

52
go.sum
View File

@@ -1,7 +1,7 @@
github.com/AdguardTeam/dnsproxy v0.60.0 h1:0gLYoFyWRhQ1MP6g6AqqZXL5/h2QM4FE1aybUZ5HGuE=
github.com/AdguardTeam/dnsproxy v0.60.0/go.mod h1:B7FvvTFQZBfey1cJXQo732EyCLX6xj4JqrciCawATzg=
github.com/AdguardTeam/golibs v0.18.0 h1:ckS2YK7t2Ub6UkXl0fnreVaM15Zb07Hh1gmFqttjpWg=
github.com/AdguardTeam/golibs v0.18.0/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
github.com/AdguardTeam/dnsproxy v0.59.1 h1:G/6T32EuPF0rhRkACkLFwD0pajI9351a1LACpuA2UcE=
github.com/AdguardTeam/dnsproxy v0.59.1/go.mod h1:ZvkbM71HwpilgkCnTubDiR4Ba6x5Qvnhy2iasMWaTDM=
github.com/AdguardTeam/golibs v0.17.2 h1:vg6wHMjUKscnyPGRvxS5kAt7Uw4YxcJiITZliZ476W8=
github.com/AdguardTeam/golibs v0.17.2/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
@@ -41,16 +41,16 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 h1:PxlBVtIFHR/mtWk2i0gTEdCz+jBnqiuHNSki0epDbVs=
github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -73,17 +73,17 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -118,10 +118,10 @@ go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -132,8 +132,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
@@ -149,8 +149,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -158,8 +158,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
@@ -171,5 +171,5 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

View File

@@ -0,0 +1,33 @@
// Package aghchan contains channel utilities.
package aghchan
import (
"fmt"
"time"
)
// Receive returns an error if it cannot receive a value form c before timeout
// runs out.
func Receive[T any](c <-chan T, timeout time.Duration) (v T, ok bool, err error) {
var zero T
timeoutCh := time.After(timeout)
select {
case <-timeoutCh:
// TODO(a.garipov): Consider implementing [errors.Aser] for
// os.ErrTimeout.
return zero, false, fmt.Errorf("did not receive after %s", timeout)
case v, ok = <-c:
return v, ok, nil
}
}
// MustReceive panics if it cannot receive a value form c before timeout runs
// out.
func MustReceive[T any](c <-chan T, timeout time.Duration) (v T, ok bool) {
v, ok, err := Receive(c, timeout)
if err != nil {
panic(err)
}
return v, ok
}

View File

@@ -1,23 +1,65 @@
package aghnet
import (
"context"
"fmt"
"io"
"io/fs"
"net/netip"
"path"
"strings"
"sync/atomic"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// DefaultHostsPaths returns the slice of paths default for the operating system
// to files and directories which are containing the hosts database. The result
// is intended to be used within fs.FS so the initial slash is omitted.
func DefaultHostsPaths() (paths []string) {
return defaultHostsPaths()
}
// MatchAddr returns the records for the IP address.
func (hc *HostsContainer) MatchAddr(ip netip.Addr) (recs []*hostsfile.Record) {
cur := hc.current.Load()
if cur == nil {
return nil
}
return cur.addrs[ip]
}
// MatchName returns the records for the hostname.
func (hc *HostsContainer) MatchName(name string) (recs []*hostsfile.Record) {
cur := hc.current.Load()
if cur != nil {
recs = cur.names[name]
}
return recs
}
// hostsContainerPrefix is a prefix for logging and wrapping errors in
// HostsContainer's methods.
const hostsContainerPrefix = "hosts container"
// Hosts is a map of IP addresses to the records, as it primarily stored in the
// [HostsContainer]. It should not be accessed for writing since it may be read
// concurrently, users should clone it before modifying.
//
// The order of records for each address is preserved from original files, but
// the order of the addresses, being a map key, is not.
//
// TODO(e.burkov): Probably, this should be a sorted slice of records.
type Hosts map[netip.Addr][]*hostsfile.Record
// HostsContainer stores the relevant hosts database provided by the OS and
// processes both A/AAAA and PTR DNS requests for those.
type HostsContainer struct {
@@ -25,10 +67,10 @@ type HostsContainer struct {
done chan struct{}
// updates is the channel for receiving updated hosts.
updates chan *hostsfile.DefaultStorage
updates chan Hosts
// current is the last set of hosts parsed.
current atomic.Pointer[hostsfile.DefaultStorage]
current atomic.Pointer[hostsIndex]
// fsys is the working file system to read hosts files from.
fsys fs.FS
@@ -69,7 +111,7 @@ func NewHostsContainer(
hc = &HostsContainer{
done: make(chan struct{}, 1),
updates: make(chan *hostsfile.DefaultStorage, 1),
updates: make(chan Hosts, 1),
fsys: fsys,
watcher: w,
patterns: patterns,
@@ -110,25 +152,11 @@ func (hc *HostsContainer) Close() (err error) {
return err
}
// Upd returns the channel into which the updates are sent. The updates
// themselves must not be modified.
func (hc *HostsContainer) Upd() (updates <-chan *hostsfile.DefaultStorage) {
// Upd returns the channel into which the updates are sent.
func (hc *HostsContainer) Upd() (updates <-chan Hosts) {
return hc.updates
}
// type check
var _ hostsfile.Storage = (*HostsContainer)(nil)
// ByAddr implements the [hostsfile.Storage] interface for *HostsContainer.
func (hc *HostsContainer) ByAddr(addr netip.Addr) (names []string) {
return hc.current.Load().ByAddr(addr)
}
// ByName implements the [hostsfile.Storage] interface for *HostsContainer.
func (hc *HostsContainer) ByName(name string) (addrs []netip.Addr) {
return hc.current.Load().ByName(name)
}
// pathsToPatterns converts paths into patterns compatible with fs.Glob.
func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error) {
for i, p := range paths {
@@ -139,7 +167,7 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
continue
}
// Don't put a filename here since it's already added by [fs.Stat].
// Don't put a filename here since it's already added by fs.Stat.
return nil, fmt.Errorf("path at index %d: %w", i, err)
}
@@ -181,7 +209,7 @@ func (hc *HostsContainer) handleEvents() {
}
// sendUpd tries to send the parsed data to the ch.
func (hc *HostsContainer) sendUpd(recs *hostsfile.DefaultStorage) {
func (hc *HostsContainer) sendUpd(recs Hosts) {
log.Debug("%s: sending upd", hostsContainerPrefix)
ch := hc.updates
@@ -198,6 +226,67 @@ func (hc *HostsContainer) sendUpd(recs *hostsfile.DefaultStorage) {
}
}
// hostsIndex is a [hostsfile.Set] to enumerate all the records.
type hostsIndex struct {
// addrs maps IP addresses to the records.
addrs Hosts
// names maps hostnames to the records.
names map[string][]*hostsfile.Record
}
// walk is a file walking function for hostsIndex.
func (idx *hostsIndex) walk(r io.Reader) (patterns []string, cont bool, err error) {
return nil, true, hostsfile.Parse(idx, r, nil)
}
// type check
var _ hostsfile.Set = (*hostsIndex)(nil)
// Add implements the [hostsfile.Set] interface for *hostsIndex.
func (idx *hostsIndex) Add(rec *hostsfile.Record) {
idx.addrs[rec.Addr] = append(idx.addrs[rec.Addr], rec)
for _, name := range rec.Names {
idx.names[name] = append(idx.names[name], rec)
}
}
// type check
var _ hostsfile.HandleSet = (*hostsIndex)(nil)
// HandleInvalid implements the [hostsfile.HandleSet] interface for *hostsIndex.
func (idx *hostsIndex) HandleInvalid(src string, _ []byte, err error) {
lineErr := &hostsfile.LineError{}
if !errors.As(err, &lineErr) {
// Must not happen if idx passed to [hostsfile.Parse].
return
} else if errors.Is(lineErr, hostsfile.ErrEmptyLine) {
// Ignore empty lines.
return
}
log.Info("%s: warning: parsing %q: %s", hostsContainerPrefix, src, lineErr)
}
// equalRecs is an equality function for [*hostsfile.Record].
func equalRecs(a, b *hostsfile.Record) (ok bool) {
return a.Addr == b.Addr && a.Source == b.Source && slices.Equal(a.Names, b.Names)
}
// equalRecSlices is an equality function for slices of [*hostsfile.Record].
func equalRecSlices(a, b []*hostsfile.Record) (ok bool) { return slices.EqualFunc(a, b, equalRecs) }
// Equal returns true if indexes are equal.
func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) {
if idx == nil {
return other == nil
} else if other == nil {
return false
}
return maps.EqualFunc(idx.addrs, other.addrs, equalRecSlices)
}
// refresh gets the data from specified files and propagates the updates if
// needed.
//
@@ -205,22 +294,63 @@ func (hc *HostsContainer) sendUpd(recs *hostsfile.DefaultStorage) {
func (hc *HostsContainer) refresh() (err error) {
log.Debug("%s: refreshing", hostsContainerPrefix)
// The error is always nil here since no readers passed.
strg, _ := hostsfile.NewDefaultStorage()
_, err = aghos.FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
// Don't wrap the error since it's already informative enough as is.
return nil, true, hostsfile.Parse(strg, r, nil)
}).Walk(hc.fsys, hc.patterns...)
var addrLen, nameLen int
last := hc.current.Load()
if last != nil {
addrLen, nameLen = len(last.addrs), len(last.names)
}
idx := &hostsIndex{
addrs: make(Hosts, addrLen),
names: make(map[string][]*hostsfile.Record, nameLen),
}
_, err = aghos.FileWalker(idx.walk).Walk(hc.fsys, hc.patterns...)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
// TODO(e.burkov): Serialize updates using [time.Time].
if !hc.current.Load().Equal(strg) {
hc.current.Store(strg)
hc.sendUpd(strg)
// TODO(e.burkov): Serialize updates using time.
if !last.Equal(idx) {
hc.current.Store(idx)
hc.sendUpd(idx.addrs)
}
return nil
}
// type check
var _ upstream.Resolver = (*HostsContainer)(nil)
// LookupNetIP implements the [upstream.Resolver] interface for *HostsContainer.
func (hc *HostsContainer) LookupNetIP(
ctx context.Context,
network string,
hostname string,
) (addrs []netip.Addr, err error) {
// TODO(e.burkov): Think of extracting this logic to a golibs function if
// needed anywhere else.
var isDesiredProto func(ip netip.Addr) (ok bool)
switch network {
case "ip4":
isDesiredProto = (netip.Addr).Is4
case "ip6":
isDesiredProto = (netip.Addr).Is6
case "ip":
isDesiredProto = func(ip netip.Addr) (ok bool) { return true }
default:
return nil, fmt.Errorf("unsupported network: %q", network)
}
idx := hc.current.Load()
recs := idx.names[strings.ToLower(hostname)]
addrs = make([]netip.Addr, 0, len(recs))
for _, rec := range recs {
if isDesiredProto(rec.Addr) {
addrs = append(addrs, rec.Addr)
}
}
return slices.Clip(addrs), nil
}

View File

@@ -0,0 +1,17 @@
//go:build linux
package aghnet
import (
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
)
func defaultHostsPaths() (paths []string) {
paths = []string{"etc/hosts"}
if aghos.IsOpenWrt() {
paths = append(paths, "tmp/hosts")
}
return paths
}

View File

@@ -0,0 +1,7 @@
//go:build !(windows || linux)
package aghnet
func defaultHostsPaths() (paths []string) {
return []string{"etc/hosts"}
}

View File

@@ -3,11 +3,13 @@ package aghnet_test
import (
"net/netip"
"path"
"path/filepath"
"sync/atomic"
"testing"
"testing/fstest"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
@@ -18,6 +20,139 @@ import (
"github.com/stretchr/testify/require"
)
// nl is a newline character.
const nl = "\n"
// Variables mirroring the etc_hosts file from testdata.
var (
addr1000 = netip.MustParseAddr("1.0.0.0")
addr1001 = netip.MustParseAddr("1.0.0.1")
addr1002 = netip.MustParseAddr("1.0.0.2")
addr1003 = netip.MustParseAddr("1.0.0.3")
addr1004 = netip.MustParseAddr("1.0.0.4")
addr1357 = netip.MustParseAddr("1.3.5.7")
addr4216 = netip.MustParseAddr("4.2.1.6")
addr7531 = netip.MustParseAddr("7.5.3.1")
addr0 = netip.MustParseAddr("::")
addr1 = netip.MustParseAddr("::1")
addr2 = netip.MustParseAddr("::2")
addr3 = netip.MustParseAddr("::3")
addr4 = netip.MustParseAddr("::4")
addr42 = netip.MustParseAddr("::42")
addr13 = netip.MustParseAddr("::13")
addr31 = netip.MustParseAddr("::31")
hostsSrc = "./" + filepath.Join("./testdata", "etc_hosts")
testHosts = map[netip.Addr][]*hostsfile.Record{
addr1000: {{
Addr: addr1000,
Source: hostsSrc,
Names: []string{"hello", "hello.world"},
}, {
Addr: addr1000,
Source: hostsSrc,
Names: []string{"hello.world.again"},
}, {
Addr: addr1000,
Source: hostsSrc,
Names: []string{"hello.world"},
}},
addr1001: {{
Addr: addr1001,
Source: hostsSrc,
Names: []string{"simplehost"},
}, {
Addr: addr1001,
Source: hostsSrc,
Names: []string{"simplehost"},
}},
addr1002: {{
Addr: addr1002,
Source: hostsSrc,
Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
}},
addr1003: {{
Addr: addr1003,
Source: hostsSrc,
Names: []string{"*"},
}},
addr1004: {{
Addr: addr1004,
Source: hostsSrc,
Names: []string{"*.com"},
}},
addr1357: {{
Addr: addr1357,
Source: hostsSrc,
Names: []string{"domain4", "domain4.alias"},
}},
addr7531: {{
Addr: addr7531,
Source: hostsSrc,
Names: []string{"domain4.alias", "domain4"},
}},
addr4216: {{
Addr: addr4216,
Source: hostsSrc,
Names: []string{"domain", "domain.alias"},
}},
addr0: {{
Addr: addr0,
Source: hostsSrc,
Names: []string{"hello", "hello.world"},
}, {
Addr: addr0,
Source: hostsSrc,
Names: []string{"hello.world.again"},
}, {
Addr: addr0,
Source: hostsSrc,
Names: []string{"hello.world"},
}},
addr1: {{
Addr: addr1,
Source: hostsSrc,
Names: []string{"simplehost"},
}, {
Addr: addr1,
Source: hostsSrc,
Names: []string{"simplehost"},
}},
addr2: {{
Addr: addr2,
Source: hostsSrc,
Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
}},
addr3: {{
Addr: addr3,
Source: hostsSrc,
Names: []string{"*"},
}},
addr4: {{
Addr: addr4,
Source: hostsSrc,
Names: []string{"*.com"},
}},
addr42: {{
Addr: addr42,
Source: hostsSrc,
Names: []string{"domain.alias", "domain"},
}},
addr13: {{
Addr: addr13,
Source: hostsSrc,
Names: []string{"domain6", "domain6.alias"},
}},
addr31: {{
Addr: addr31,
Source: hostsSrc,
Names: []string{"domain6.alias", "domain6"},
}},
}
)
func TestNewHostsContainer(t *testing.T) {
const dirname = "dir"
const filename = "file1"
@@ -132,21 +267,7 @@ func TestHostsContainer_refresh(t *testing.T) {
anotherIPStr := "1.2.3.4"
anotherIP := netip.MustParseAddr(anotherIPStr)
r1 := &hostsfile.Record{
Addr: ip,
Source: "file1",
Names: []string{"hostname"},
}
r2 := &hostsfile.Record{
Addr: anotherIP,
Source: "file2",
Names: []string{"alias"},
}
r1Data, _ := r1.MarshalText()
r2Data, _ := r2.MarshalText()
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: r1Data}}
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
// event is a convenient alias for an empty struct{} to emit test events.
type event = struct{}
@@ -168,47 +289,172 @@ func TestHostsContainer_refresh(t *testing.T) {
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
strg, _ := hostsfile.NewDefaultStorage()
strg.Add(r1)
checkRefresh := func(t *testing.T, want aghnet.Hosts) {
t.Helper()
upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
require.True(t, ok)
assert.Equal(t, want, upd)
}
t.Run("initial_refresh", func(t *testing.T) {
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
assert.True(t, strg.Equal(upd))
checkRefresh(t, aghnet.Hosts{
ip: {{
Addr: ip,
Source: "file1",
Names: []string{"hostname"},
}},
})
})
strg.Add(r2)
t.Run("second_refresh", func(t *testing.T) {
testFS["dir/file2"] = &fstest.MapFile{Data: r2Data}
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(anotherIPStr + ` alias` + nl)}
eventsCh <- event{}
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
assert.True(t, strg.Equal(upd))
checkRefresh(t, aghnet.Hosts{
ip: {{
Addr: ip,
Source: "file1",
Names: []string{"hostname"},
}},
anotherIP: {{
Addr: anotherIP,
Source: "file2",
Names: []string{"alias"},
}},
})
})
t.Run("double_refresh", func(t *testing.T) {
// Make a change once.
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + " alias\n")}
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
eventsCh <- event{}
// Require the changes are written.
current, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
require.Eventually(t, func() bool {
ips := hc.MatchName("hostname")
require.Empty(t, current.ByName("hostname"))
return len(ips) == 0
}, 5*time.Second, time.Second/2)
// Make a change again.
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + " hostname\n")}
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}
eventsCh <- event{}
// Require the changes are written.
current, ok = testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
require.True(t, ok)
require.Eventually(t, func() bool {
ips := hc.MatchName("hostname")
require.NotEmpty(t, current.ByName("hostname"))
return len(ips) > 0
}, 5*time.Second, time.Second/2)
assert.Len(t, hc.Upd(), 1)
})
}
func TestHostsContainer_MatchName(t *testing.T) {
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
stubWatcher := aghtest.FSWatcher{
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
OnClose: func() (err error) { return nil },
}
testCases := []struct {
req string
name string
want []*hostsfile.Record
}{{
req: "simplehost",
name: "simple",
want: append(testHosts[addr1001], testHosts[addr1]...),
}, {
req: "hello.world",
name: "hello_alias",
want: []*hostsfile.Record{
testHosts[addr1000][0],
testHosts[addr1000][2],
testHosts[addr0][0],
testHosts[addr0][2],
},
}, {
req: "hello.world.again",
name: "other_line_alias",
want: []*hostsfile.Record{
testHosts[addr1000][1],
testHosts[addr0][1],
},
}, {
req: "say.hello",
name: "hello_subdomain",
want: nil,
}, {
req: "say.hello.world",
name: "hello_alias_subdomain",
want: nil,
}, {
req: "for.testing",
name: "lots_of_aliases",
want: append(testHosts[addr1002], testHosts[addr2]...),
}, {
req: "nonexistent.example",
name: "non-existing",
want: nil,
}, {
req: "domain",
name: "issue_4216_4_6",
want: append(testHosts[addr4216], testHosts[addr42]...),
}, {
req: "domain4",
name: "issue_4216_4",
want: append(testHosts[addr1357], testHosts[addr7531]...),
}, {
req: "domain6",
name: "issue_4216_6",
want: append(testHosts[addr13], testHosts[addr31]...),
}}
hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
recs := hc.MatchName(tc.req)
assert.Equal(t, tc.want, recs)
})
}
}
func TestHostsContainer_MatchAddr(t *testing.T) {
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
stubWatcher := aghtest.FSWatcher{
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
OnClose: func() (err error) { return nil },
}
hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
testCases := []struct {
req netip.Addr
name string
want []*hostsfile.Record
}{{
req: netip.AddrFrom4([4]byte{1, 0, 0, 1}),
name: "reverse",
want: testHosts[addr1001],
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
recs := hc.MatchAddr(tc.req)
assert.Equal(t, tc.want, recs)
})
}
}

View File

@@ -0,0 +1,32 @@
//go:build windows
package aghnet
import (
"os"
"path"
"path/filepath"
"strings"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/windows"
)
func defaultHostsPaths() (paths []string) {
sysDir, err := windows.GetSystemDirectory()
if err != nil {
log.Error("aghnet: getting system directory: %s", err)
return []string{}
}
// Split all the elements of the path to join them afterwards. This is
// needed to make the Windows-specific path string returned by
// windows.GetSystemDirectory to be compatible with fs.FS.
pathElems := strings.Split(sysDir, string(os.PathSeparator))
if len(pathElems) > 0 && pathElems[0] == filepath.VolumeName(sysDir) {
pathElems = pathElems[1:]
}
return []string{path.Join(append(pathElems, "drivers/etc/hosts")...)}
}

View File

@@ -1,9 +1,11 @@
package aghnet_test
import (
"io/fs"
"net"
"net/netip"
"net/url"
"os"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
@@ -16,6 +18,9 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
// testdata is the filesystem containing data for testing the package.
var testdata fs.FS = os.DirFS("./testdata")
func TestParseAddrPort(t *testing.T) {
const defaultPort = 1

38
internal/aghnet/testdata/etc_hosts vendored Normal file
View File

@@ -0,0 +1,38 @@
#
# Test /etc/hosts file
#
1.0.0.1 simplehost
1.0.0.0 hello hello.world
# See https://github.com/AdguardTeam/AdGuardHome/issues/3846.
1.0.0.2 a.whole lot.of aliases for.testing
# See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
1.0.0.3 *
1.0.0.4 *.com
# See https://github.com/AdguardTeam/AdGuardHome/issues/4079.
1.0.0.0 hello.world.again
# Duplicates of a main host and an alias.
1.0.0.1 simplehost
1.0.0.0 hello.world
# Same for IPv6.
::1 simplehost
:: hello hello.world
::2 a.whole lot.of aliases for.testing
::3 *
::4 *.com
:: hello.world.again
::1 simplehost
:: hello.world
# See https://github.com/AdguardTeam/AdGuardHome/issues/4216.
4.2.1.6 domain domain.alias
::42 domain.alias domain
1.3.5.7 domain4 domain4.alias
7.5.3.1 domain4.alias domain4
::13 domain6 domain6.alias
::31 domain6.alias domain6

1
internal/aghnet/testdata/ifaces vendored Normal file
View File

@@ -0,0 +1 @@
iface sample_name inet static

View File

@@ -0,0 +1,5 @@
# The "testdata" part is added here because the test is actually run from the
# parent directory. Real interface files usually contain only absolute paths.
source ./testdata/ifaces
source ./testdata/*

View File

@@ -67,7 +67,7 @@ type Config struct {
RatelimitSubnetLenIPv6 int `yaml:"ratelimit_subnet_len_ipv6"`
// RatelimitWhitelist is the list of whitelisted client IP addresses.
RatelimitWhitelist []netip.Addr `yaml:"ratelimit_whitelist"`
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"`
// RefuseAny, if true, refuse ANY requests.
RefuseAny bool `yaml:"refuse_any"`
@@ -298,24 +298,24 @@ type ServerConfig struct {
func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
srvConf := s.conf
conf = &proxy.Config{
HTTP3: srvConf.ServeHTTP3,
Ratelimit: int(srvConf.Ratelimit),
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
RatelimitWhitelist: srvConf.RatelimitWhitelist,
RefuseAny: srvConf.RefuseAny,
TrustedProxies: srvConf.TrustedProxies,
CacheMinTTL: srvConf.CacheMinTTL,
CacheMaxTTL: srvConf.CacheMaxTTL,
CacheOptimistic: srvConf.CacheOptimistic,
UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64,
DNS64Prefs: srvConf.DNS64Prefixes,
HTTP3: srvConf.ServeHTTP3,
Ratelimit: int(srvConf.Ratelimit),
RatelimitSubnetMaskIPv4: net.CIDRMask(srvConf.RatelimitSubnetLenIPv4, netutil.IPv4BitLen),
RatelimitSubnetMaskIPv6: net.CIDRMask(srvConf.RatelimitSubnetLenIPv6, netutil.IPv6BitLen),
RatelimitWhitelist: srvConf.RatelimitWhitelist,
RefuseAny: srvConf.RefuseAny,
TrustedProxies: srvConf.TrustedProxies,
CacheMinTTL: srvConf.CacheMinTTL,
CacheMaxTTL: srvConf.CacheMaxTTL,
CacheOptimistic: srvConf.CacheOptimistic,
UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64,
DNS64Prefs: srvConf.DNS64Prefixes,
}
if srvConf.EDNSClientSubnet.UseCustom {

View File

@@ -1,349 +0,0 @@
package dnsforward
import (
"fmt"
"strings"
"sync"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// upstreamConfigValidator parses the [*proxy.UpstreamConfig] and checks the
// actual DNS availability of each upstream.
type upstreamConfigValidator struct {
// general is the general upstream configuration.
general []*upstreamResult
// fallback is the fallback upstream configuration.
fallback []*upstreamResult
// private is the private upstream configuration.
private []*upstreamResult
}
// upstreamResult is a result of validation of an [upstream.Upstream] within an
// [proxy.UpstreamConfig].
type upstreamResult struct {
// server is the parsed upstream. It is nil when there was an error during
// parsing.
server upstream.Upstream
// err is the error either from parsing or from checking the upstream.
err error
// original is the piece of configuration that have either been turned to an
// upstream or caused an error.
original string
// isSpecific is true if the upstream is domain-specific.
isSpecific bool
}
// compare compares two [upstreamResult]s. It returns 0 if they are equal, -1
// if ur should be sorted before other, and 1 otherwise.
//
// TODO(e.burkov): Perhaps it makes sense to sort the results with errors near
// the end.
func (ur *upstreamResult) compare(other *upstreamResult) (res int) {
return strings.Compare(ur.original, other.original)
}
// newUpstreamConfigValidator parses the upstream configuration and returns a
// validator for it. cv already contains the parsed upstreams along with errors
// related.
func newUpstreamConfigValidator(
general []string,
fallback []string,
private []string,
opts *upstream.Options,
) (cv *upstreamConfigValidator) {
cv = &upstreamConfigValidator{}
for _, line := range general {
cv.general = cv.insertLineResults(cv.general, line, opts)
}
for _, line := range fallback {
cv.fallback = cv.insertLineResults(cv.fallback, line, opts)
}
for _, line := range private {
cv.private = cv.insertLineResults(cv.private, line, opts)
}
return cv
}
// insertLineResults parses line and inserts the result into s. It can insert
// multiple results as well as none.
func (cv *upstreamConfigValidator) insertLineResults(
s []*upstreamResult,
line string,
opts *upstream.Options,
) (result []*upstreamResult) {
upstreams, isSpecific, err := splitUpstreamLine(line)
if err != nil {
return cv.insert(s, &upstreamResult{
err: err,
original: line,
})
}
for _, upstreamAddr := range upstreams {
var res *upstreamResult
if upstreamAddr != "#" {
res = cv.parseUpstream(upstreamAddr, opts)
} else if !isSpecific {
res = &upstreamResult{
err: errNotDomainSpecific,
original: upstreamAddr,
}
} else {
continue
}
res.isSpecific = isSpecific
s = cv.insert(s, res)
}
return s
}
// insert inserts r into slice in a sorted order, except duplicates. slice must
// not be nil.
func (cv *upstreamConfigValidator) insert(
s []*upstreamResult,
r *upstreamResult,
) (result []*upstreamResult) {
i, has := slices.BinarySearchFunc(s, r, (*upstreamResult).compare)
if has {
log.Debug("dnsforward: duplicate configuration %q", r.original)
return s
}
return slices.Insert(s, i, r)
}
// parseUpstream parses addr and returns the result of parsing. It returns nil
// if the specified server points at the default upstream server which is
// validated separately.
func (cv *upstreamConfigValidator) parseUpstream(
addr string,
opts *upstream.Options,
) (r *upstreamResult) {
// Check if the upstream has a valid protocol prefix.
//
// TODO(e.burkov): Validate the domain name.
if proto, _, ok := strings.Cut(addr, "://"); ok {
if !slices.Contains(protocols, proto) {
return &upstreamResult{
err: fmt.Errorf("bad protocol %q", proto),
original: addr,
}
}
}
ups, err := upstream.AddressToUpstream(addr, opts)
return &upstreamResult{
server: ups,
err: err,
original: addr,
}
}
// check tries to exchange with each successfully parsed upstream and enriches
// the results with the healthcheck errors. It should not be called after the
// [upsConfValidator.close] method, since it makes no sense to check the closed
// upstreams.
func (cv *upstreamConfigValidator) check() {
const (
// testTLD is the special-use fully-qualified domain name for testing
// the DNS server reachability.
//
// See https://datatracker.ietf.org/doc/html/rfc6761#section-6.2.
testTLD = "test."
// inAddrARPATLD is the special-use fully-qualified domain name for PTR
// IP address resolution.
//
// See https://datatracker.ietf.org/doc/html/rfc1035#section-3.5.
inAddrARPATLD = "in-addr.arpa."
)
commonChecker := &healthchecker{
hostname: testTLD,
qtype: dns.TypeA,
ansEmpty: true,
}
arpaChecker := &healthchecker{
hostname: inAddrARPATLD,
qtype: dns.TypePTR,
ansEmpty: false,
}
wg := &sync.WaitGroup{}
wg.Add(len(cv.general) + len(cv.fallback) + len(cv.private))
for _, res := range cv.general {
go cv.checkSrv(res, wg, commonChecker)
}
for _, res := range cv.fallback {
go cv.checkSrv(res, wg, commonChecker)
}
for _, res := range cv.private {
go cv.checkSrv(res, wg, arpaChecker)
}
wg.Wait()
}
// checkSrv runs hc on the server from res, if any, and stores any occurred
// error in res. wg is always marked done in the end. It used to be called in
// a separate goroutine.
func (cv *upstreamConfigValidator) checkSrv(
res *upstreamResult,
wg *sync.WaitGroup,
hc *healthchecker,
) {
defer wg.Done()
if res.server == nil {
return
}
res.err = hc.check(res.server)
if res.err != nil && res.isSpecific {
res.err = domainSpecificTestError{Err: res.err}
}
}
// close closes all the upstreams that were successfully parsed. It enriches
// the results with deferred closing errors.
func (cv *upstreamConfigValidator) close() {
for _, slice := range [][]*upstreamResult{cv.general, cv.fallback, cv.private} {
for _, r := range slice {
if r.server != nil {
r.err = errors.WithDeferred(r.err, r.server.Close())
}
}
}
}
// status returns all the data collected during parsing, healthcheck, and
// closing of the upstreams. The returned map is keyed by the original upstream
// configuration piece and contains the corresponding error or "OK" if there was
// no error.
func (cv *upstreamConfigValidator) status() (results map[string]string) {
result := map[string]string{}
for _, res := range cv.general {
resultToStatus("general", res, result)
}
for _, res := range cv.fallback {
resultToStatus("fallback", res, result)
}
for _, res := range cv.private {
resultToStatus("private", res, result)
}
return result
}
// resultToStatus puts "OK" or an error message from res into resMap. section
// is the name of the upstream configuration section, i.e. "general",
// "fallback", or "private", and only used for logging.
//
// TODO(e.burkov): Currently, the HTTP handler expects that all the results are
// put together in a single map, which may lead to collisions, see AG-27539.
// Improve the results compilation.
func resultToStatus(section string, res *upstreamResult, resMap map[string]string) {
val := "OK"
if res.err != nil {
val = res.err.Error()
}
prevVal := resMap[res.original]
switch prevVal {
case "":
resMap[res.original] = val
case val:
log.Debug("dnsforward: duplicating %s config line %q", section, res.original)
default:
log.Debug(
"dnsforward: warning: %s config line %q (%v) had different result %v",
section,
val,
res.original,
prevVal,
)
}
}
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
// the tested upstream domain-specific and therefore consider its errors
// non-critical.
//
// TODO(a.garipov): Some common mechanism of distinguishing between errors and
// warnings (non-critical errors) is desired.
type domainSpecificTestError struct {
// Err is the actual error occurred during healthcheck test.
Err error
}
// type check
var _ error = domainSpecificTestError{}
// Error implements the [error] interface for domainSpecificTestError.
func (err domainSpecificTestError) Error() (msg string) {
return fmt.Sprintf("WARNING: %s", err.Err)
}
// type check
var _ errors.Wrapper = domainSpecificTestError{}
// Unwrap implements the [errors.Wrapper] interface for domainSpecificTestError.
func (err domainSpecificTestError) Unwrap() (wrapped error) {
return err.Err
}
// healthchecker checks the upstream's status by exchanging with it.
type healthchecker struct {
// hostname is the name of the host to put into healthcheck DNS request.
hostname string
// qtype is the type of DNS request to use for healthcheck.
qtype uint16
// ansEmpty defines if the answer section within the response is expected to
// be empty.
ansEmpty bool
}
// check exchanges with u and validates the response.
func (h *healthchecker) check(u upstream.Upstream) (err error) {
req := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: true,
},
Question: []dns.Question{{
Name: h.hostname,
Qtype: h.qtype,
Qclass: dns.ClassINET,
}},
}
reply, err := u.Exchange(req)
if err != nil {
return fmt.Errorf("couldn't communicate with upstream: %w", err)
} else if h.ansEmpty && len(reply.Answer) > 0 {
return errWrongResponse
}
return nil
}

View File

@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"net"
"net/netip"
"strconv"
"time"
"github.com/AdguardTeam/golibs/errors"
@@ -13,12 +11,10 @@ import (
)
// DialContext is an [aghnet.DialContextFunc] that uses s to resolve hostnames.
// addr should be a valid host:port address, where host could be a domain name
// or an IP address.
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, portStr, err := net.SplitHostPort(addr)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
@@ -32,24 +28,21 @@ func (s *Server) DialContext(ctx context.Context, network, addr string) (conn ne
return dialer.DialContext(ctx, network, addr)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port %s: %w", portStr, err)
}
ips, err := s.Resolve(ctx, network, host)
addrs, err := s.Resolve(host)
if err != nil {
return nil, fmt.Errorf("resolving %q: %w", host, err)
} else if len(ips) == 0 {
}
log.Debug("dnsforward: resolving %q: %v", host, addrs)
if len(addrs) == 0 {
return nil, fmt.Errorf("no addresses for host %q", host)
}
log.Debug("dnsforward: resolved %q: %v", host, ips)
var dialErrs []error
for _, ip := range ips {
addrPort := netip.AddrPortFrom(ip, uint16(port))
conn, err = dialer.DialContext(ctx, network, addrPort.String())
for _, a := range addrs {
addr = net.JoinHostPort(a.String(), port)
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
dialErrs = append(dialErrs, err)

View File

@@ -2,7 +2,6 @@
package dnsforward
import (
"context"
"fmt"
"io"
"net"
@@ -36,11 +35,6 @@ import (
// DefaultTimeout is the default upstream timeout
const DefaultTimeout = 10 * time.Second
// defaultLocalTimeout is the default timeout for resolving addresses from
// locally-served networks. It is assumed that local resolvers should work much
// faster than ordinary upstreams.
const defaultLocalTimeout = 1 * time.Second
// defaultClientIDCacheCount is the default count of items in the LRU ClientID
// cache. The assumption here is that there won't be more than this many
// requests between the BeforeRequestHandler stage and the actual processing.
@@ -142,7 +136,7 @@ type Server struct {
// PTR resolving.
sysResolvers SystemResolvers
// etcHosts contains the current data from the system's hosts files.
// etcHosts contains the data from the system's hosts files.
etcHosts upstream.Resolver
// bootstrap is the resolver for upstreams' hostnames.
@@ -239,11 +233,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
p.Anonymizer = aghnet.NewIPMut(nil)
}
var etcHosts upstream.Resolver
if p.EtcHosts != nil {
etcHosts = upstream.NewHostsResolver(p.EtcHosts)
}
s = &Server{
dnsFilter: p.DNSFilter,
dhcpServer: p.DHCPServer,
@@ -252,7 +241,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
privateNets: p.PrivateNets,
// TODO(e.burkov): Use some case-insensitive string comparison.
localDomainSuffix: strings.ToLower(localDomainSuffix),
etcHosts: etcHosts,
etcHosts: p.EtcHosts,
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
clientIDCache: cache.New(cache.Config{
EnableLRU: true,
@@ -304,7 +293,7 @@ func (s *Server) WriteDiskConfig(c *Config) {
sc := s.conf.Config
*c = sc
c.RatelimitWhitelist = slices.Clone(sc.RatelimitWhitelist)
c.RatelimitWhitelist = stringutil.CloneSlice(sc.RatelimitWhitelist)
c.BootstrapDNS = stringutil.CloneSlice(sc.BootstrapDNS)
c.FallbackDNS = stringutil.CloneSlice(sc.FallbackDNS)
c.AllowedClients = stringutil.CloneSlice(sc.AllowedClients)
@@ -335,14 +324,15 @@ func (s *Server) AddrProcConfig() (c *client.DefaultAddrProcConfig) {
}
}
// Resolve gets IP addresses by host name from an upstream server. No
// request/response filtering is performed. Query log and Stats are not
// updated. This method may be called before [Server.Start].
func (s *Server) Resolve(ctx context.Context, net, host string) (addr []netip.Addr, err error) {
// Resolve - get IP addresses by host name from an upstream server.
// No request/response filtering is performed.
// Query log and Stats are not updated.
// This method may be called before Start().
func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return s.internalProxy.LookupNetIP(ctx, net, host)
return s.internalProxy.LookupIPAddr(host)
}
const (
@@ -469,6 +459,11 @@ func (s *Server) startLocked() error {
return err
}
// defaultLocalTimeout is the default timeout for resolving addresses from
// locally-served networks. It is assumed that local resolvers should work much
// faster than ordinary upstreams.
const defaultLocalTimeout = 1 * time.Second
// setupLocalResolvers initializes the resolvers for local addresses. It
// assumes s.serverLock is locked or the Server not running.
func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
@@ -606,7 +601,7 @@ func (s *Server) prepareInternalDNS() (boot upstream.Resolver, err error) {
return nil, err
}
err = s.prepareUpstreamSettings(s.bootstrap)
err = s.prepareUpstreamSettings(boot)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return s.bootstrap, err

View File

@@ -54,10 +54,13 @@ const (
testMessagesCount = 10
)
// testClientAddrPort is the common net.Addr for tests.
// testClientAddr is the common net.Addr for tests.
//
// TODO(a.garipov): Use more.
var testClientAddrPort = netip.MustParseAddrPort("1.2.3.4:12345")
var testClientAddr net.Addr = &net.TCPAddr{
IP: net.IP{1, 2, 3, 4},
Port: 12345,
}
func startDeferStop(t *testing.T, s *Server) {
t.Helper()

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
@@ -27,7 +28,8 @@ func (s *Server) beforeRequestHandler(
return false, fmt.Errorf("getting clientid: %w", err)
}
blocked, _ := s.IsBlockedClient(pctx.Addr.Addr(), clientID)
addrPort := netutil.NetAddrToAddrPort(pctx.Addr)
blocked, _ := s.IsBlockedClient(addrPort.Addr(), clientID)
if blocked {
return s.preBlockedResponse(pctx)
}
@@ -58,7 +60,8 @@ func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filter
setts = s.dnsFilter.Settings()
setts.ProtectionEnabled = dctx.protectionEnabled
if s.conf.FilterHandler != nil {
s.conf.FilterHandler(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
addrPort := netutil.NetAddrToAddrPort(dctx.proxyCtx.Addr)
s.conf.FilterHandler(addrPort.Addr(), dctx.clientID, setts)
}
return setts

View File

@@ -187,7 +187,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
dctx := &proxy.DNSContext{
Proto: proxy.ProtoUDP,
Req: tc.req,
Addr: testClientAddrPort,
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
}
t.Run(tc.name, func(t *testing.T) {
@@ -326,7 +326,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
Proto: proxy.ProtoUDP,
Req: tc.req,
Res: resp,
Addr: testClientAddrPort,
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
}
dctx := &dnsContext{

View File

@@ -4,17 +4,23 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
@@ -52,7 +58,7 @@ type jsonDNSConfig struct {
RatelimitSubnetLenIPv6 *int `json:"ratelimit_subnet_len_ipv6"`
// RatelimitWhitelist is a list of IP addresses excluded from rate limiting.
RatelimitWhitelist *[]netip.Addr `json:"ratelimit_whitelist"`
RatelimitWhitelist *[]string `json:"ratelimit_whitelist"`
// BlockingMode defines the way blocked responses are constructed.
BlockingMode *filtering.BlockingMode `json:"blocking_mode"`
@@ -129,7 +135,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
ratelimit := s.conf.Ratelimit
ratelimitSubnetLenIPv4 := s.conf.RatelimitSubnetLenIPv4
ratelimitSubnetLenIPv6 := s.conf.RatelimitSubnetLenIPv6
ratelimitWhitelist := append([]netip.Addr{}, s.conf.RatelimitWhitelist...)
ratelimitWhitelist := stringutil.CloneSliceOrEmpty(s.conf.RatelimitWhitelist)
customIP := s.conf.EDNSClientSubnet.CustomIP
enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
@@ -291,6 +297,12 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
return err
}
err = req.checkRatelimitWhitelist()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = req.checkBlockingMode()
if err != nil {
// Don't wrap the error since it's informative enough as is.
@@ -399,6 +411,21 @@ func checkInclusion(ptr *int, minN, maxN int) (err error) {
return nil
}
// checkRatelimitWhitelist returns an error if any of IP addresses is invalid.
func (req *jsonDNSConfig) checkRatelimitWhitelist() (err error) {
if req.RatelimitWhitelist == nil {
return nil
}
for i, ipStr := range *req.RatelimitWhitelist {
if _, err = netip.ParseAddr(ipStr); err != nil {
return fmt.Errorf("ratelimit whitelist: at index %d: %w", i, err)
}
}
return nil
}
// handleSetConfig handles requests to the POST /control/dns_config endpoint.
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
req := &jsonDNSConfig{}
@@ -519,6 +546,365 @@ type upstreamJSON struct {
PrivateUpstreams []string `json:"private_upstream"`
}
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
// This function is useful for filtering out non-upstream lines from upstream
// configs.
func IsCommentOrEmpty(s string) (ok bool) {
return len(s) == 0 || s[0] == '#'
}
// newUpstreamConfig validates upstreams and returns an appropriate upstream
// configuration or nil if it can't be built.
//
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
// slice already so that this function may be considered useless.
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
// No need to validate comments and empty lines.
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
if len(upstreams) == 0 {
// Consider this case valid since it means the default server should be
// used.
return nil, nil
}
err = validateUpstreamConfig(upstreams)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
conf, err = proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: net.DefaultResolver,
Timeout: DefaultTimeout,
},
)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
} else if len(conf.Upstreams) == 0 {
return nil, errors.Error("no default upstreams specified")
}
return conf, nil
}
// validateUpstreamConfig validates each upstream from the upstream
// configuration and returns an error if any upstream is invalid.
//
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
func validateUpstreamConfig(conf []string) (err error) {
for _, u := range conf {
var ups []string
var domains []string
ups, domains, err = separateUpstream(u)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
for _, addr := range ups {
_, err = validateUpstream(addr, domains)
if err != nil {
return fmt.Errorf("validating upstream %q: %w", addr, err)
}
}
}
return nil
}
// ValidateUpstreams validates each upstream and returns an error if any
// upstream is invalid or if there are no default upstreams specified.
//
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
func ValidateUpstreams(upstreams []string) (err error) {
_, err = newUpstreamConfig(upstreams)
return err
}
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
// upstream is invalid or if there are no default upstreams specified. It also
// checks each domain of domain-specific upstreams for being ARPA pointing to
// a locally-served network. privateNets must not be nil.
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
conf, err := newUpstreamConfig(upstreams)
if err != nil {
return fmt.Errorf("creating config: %w", err)
}
if conf == nil {
return nil
}
keys := maps.Keys(conf.DomainReservedUpstreams)
slices.Sort(keys)
var errs []error
for _, domain := range keys {
var subnet netip.Prefix
subnet, err = extractARPASubnet(domain)
if err != nil {
errs = append(errs, err)
continue
}
if !privateNets.Contains(subnet.Addr().AsSlice()) {
errs = append(
errs,
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
)
}
}
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
}
var protocols = []string{
"h3://",
"https://",
"quic://",
"sdns://",
"tcp://",
"tls://",
"udp://",
}
// validateUpstream returns an error if u alongside with domains is not a valid
// upstream configuration. useDefault is true if the upstream is
// domain-specific and is configured to point at the default upstream server
// which is validated separately. The upstream is considered domain-specific
// only if domains is at least not nil.
func validateUpstream(u string, domains []string) (useDefault bool, err error) {
// The special server address '#' means that default server must be used.
if useDefault = u == "#" && domains != nil; useDefault {
return useDefault, nil
}
// Check if the upstream has a valid protocol prefix.
//
// TODO(e.burkov): Validate the domain name.
for _, proto := range protocols {
if strings.HasPrefix(u, proto) {
return false, nil
}
}
if proto, _, ok := strings.Cut(u, "://"); ok {
return false, fmt.Errorf("bad protocol %q", proto)
}
// Check if upstream is either an IP or IP with port.
if _, err = netip.ParseAddr(u); err == nil {
return false, nil
} else if _, err = netip.ParseAddrPort(u); err == nil {
return false, nil
}
return false, err
}
// separateUpstream returns the upstreams and the specified domains. domains
// is nil when the upstream is not domains-specific. Otherwise it may also be
// empty.
func separateUpstream(upstreamStr string) (upstreams, domains []string, err error) {
if !strings.HasPrefix(upstreamStr, "[/") {
return []string{upstreamStr}, nil, nil
}
defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }()
parts := strings.Split(upstreamStr[2:], "/]")
switch len(parts) {
case 2:
// Go on.
case 1:
return nil, nil, errors.Error("missing separator")
default:
return nil, nil, errors.Error("duplicated separator")
}
for i, host := range strings.Split(parts[0], "/") {
if host == "" {
continue
}
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
if err != nil {
return nil, nil, fmt.Errorf("domain at index %d: %w", i, err)
}
domains = append(domains, host)
}
return strings.Fields(parts[1]), domains, nil
}
// healthCheckFunc is a signature of function to check if upstream exchanges
// properly.
type healthCheckFunc func(u upstream.Upstream) (err error)
// checkDNSUpstreamExc checks if the DNS upstream exchanges correctly.
func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
// testTLD is the special-use fully-qualified domain name for testing the
// DNS server reachability.
//
// See https://datatracker.ietf.org/doc/html/rfc6761#section-6.2.
const testTLD = "test."
req := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: true,
},
Question: []dns.Question{{
Name: testTLD,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}},
}
var reply *dns.Msg
reply, err = u.Exchange(req)
if err != nil {
return fmt.Errorf("couldn't communicate with upstream: %w", err)
} else if len(reply.Answer) != 0 {
return errors.Error("wrong response")
}
return nil
}
// checkPrivateUpstreamExc checks if the upstream for resolving private
// addresses exchanges correctly.
//
// TODO(e.burkov): Think about testing the ip6.arpa. as well.
func checkPrivateUpstreamExc(u upstream.Upstream) (err error) {
// inAddrArpaTLD is the special-use fully-qualified domain name for PTR IP
// address resolution.
//
// See https://datatracker.ietf.org/doc/html/rfc1035#section-3.5.
const inAddrArpaTLD = "in-addr.arpa."
req := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: true,
},
Question: []dns.Question{{
Name: inAddrArpaTLD,
Qtype: dns.TypePTR,
Qclass: dns.ClassINET,
}},
}
if _, err = u.Exchange(req); err != nil {
return fmt.Errorf("couldn't communicate with upstream: %w", err)
}
return nil
}
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
// the tested upstream domain-specific and therefore consider its errors
// non-critical.
//
// TODO(a.garipov): Some common mechanism of distinguishing between errors and
// warnings (non-critical errors) is desired.
type domainSpecificTestError struct {
error
}
// Error implements the [error] interface for domainSpecificTestError.
func (err domainSpecificTestError) Error() (msg string) {
return fmt.Sprintf("WARNING: %s", err.error)
}
// checkDNS parses line, creates DNS upstreams using opts, and checks if the
// upstreams are exchanging correctly. It saves the result into a sync.Map
// where key is an upstream address and value is "OK", if the upstream
// exchanges correctly, or text of the error. It is intended to be used as a
// goroutine.
//
// TODO(s.chzhen): Separate to a different structure/file.
func (s *Server) checkDNS(
line string,
opts *upstream.Options,
check healthCheckFunc,
wg *sync.WaitGroup,
m *sync.Map,
) {
defer wg.Done()
defer log.OnPanic("dnsforward: checking upstreams")
upstreams, domains, err := separateUpstream(line)
if err != nil {
err = fmt.Errorf("wrong upstream format: %w", err)
m.Store(line, err.Error())
return
}
specific := len(domains) > 0
for _, upstreamAddr := range upstreams {
var useDefault bool
useDefault, err = validateUpstream(upstreamAddr, domains)
if err != nil {
err = fmt.Errorf("wrong upstream format: %w", err)
m.Store(upstreamAddr, err.Error())
continue
}
if useDefault {
continue
}
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
err = s.checkUpstreamAddr(upstreamAddr, specific, opts, check)
if err != nil {
m.Store(upstreamAddr, err.Error())
} else {
m.Store(upstreamAddr, "OK")
}
}
}
// checkUpstreamAddr creates the DNS upstream using opts and information from
// [s.dnsFilter.EtcHosts]. Checks if the DNS upstream exchanges correctly. It
// returns an error if addr is not valid DNS upstream address or the upstream
// is not exchanging correctly.
func (s *Server) checkUpstreamAddr(
addr string,
specific bool,
opts *upstream.Options,
check healthCheckFunc,
) (err error) {
defer func() {
if err != nil && specific {
err = domainSpecificTestError{error: err}
}
}()
u, err := upstream.AddressToUpstream(addr, &upstream.Options{
Bootstrap: opts.Bootstrap,
Timeout: opts.Timeout,
PreferIPv6: opts.PreferIPv6,
})
if err != nil {
return fmt.Errorf("creating upstream for %q: %w", addr, err)
}
defer func() { err = errors.WithDeferred(err, u.Close()) }()
return check(u)
}
// closeBoots closes all the provided bootstrap servers and logs errors if any.
func closeBoots(boots []*upstream.UpstreamResolver) {
for _, c := range boots {
@@ -556,11 +942,36 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
}
defer closeBoots(boots)
cv := newUpstreamConfigValidator(req.Upstreams, req.FallbackDNS, req.PrivateUpstreams, opts)
cv.check()
cv.close()
wg := &sync.WaitGroup{}
m := &sync.Map{}
aghhttp.WriteJSONResponseOK(w, r, cv.status())
wg.Add(len(req.Upstreams) + len(req.FallbackDNS) + len(req.PrivateUpstreams))
for _, ups := range req.Upstreams {
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
}
for _, ups := range req.FallbackDNS {
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
}
for _, ups := range req.PrivateUpstreams {
go s.checkDNS(ups, opts, checkPrivateUpstreamExc, wg, m)
}
wg.Wait()
result := map[string]string{}
m.Range(func(k, v any) bool {
// TODO(e.burkov): The upstreams used for both common and private
// resolving should be reported separately.
ups := k.(string)
status := v.(string)
result[ups] = status
return true
})
aghhttp.WriteJSONResponseOK(w, r, result)
}
// handleCacheClear is the handler for the POST /control/cache_clear HTTP API.

View File

@@ -20,7 +20,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
@@ -196,8 +195,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
name: "ratelimit_subnet_len",
wantSet: "",
}, {
name: "ratelimit_whitelist_not_ip",
wantSet: `decoding request: ParseAddr("not.ip"): unexpected character (at "not.ip")`,
name: "ratelimit_whitelist_not_ip",
wantSet: `validating dns config: ratelimit whitelist: at index 1: ParseAddr("not.ip"): ` +
`unexpected character (at "not.ip")`,
}, {
name: "edns_cs_enabled",
wantSet: "",
@@ -363,7 +363,7 @@ func TestValidateUpstreams(t *testing.T) {
set: []string{"123.3.7m"},
}, {
name: "invalid",
wantErr: `splitting upstream line "[/host.com]tls://dns.adguard.com": ` +
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": ` +
`missing separator`,
set: []string{"[/host.com]tls://dns.adguard.com"},
}, {
@@ -389,7 +389,7 @@ func TestValidateUpstreams(t *testing.T) {
},
}, {
name: "bad_domain",
wantErr: `splitting upstream line "[/!/]8.8.8.8": domain at index 0: ` +
wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` +
`bad domain name "!": bad top-level domain name label "!": ` +
`bad top-level domain name label rune '!'`,
set: []string{"[/!/]8.8.8.8"},
@@ -477,15 +477,25 @@ func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (r
}
func TestServer_HandleTestUpstreamDNS(t *testing.T) {
hdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, err)
})
badHandler := dns.HandlerFunc(func(w dns.ResponseWriter, _ *dns.Msg) {
err := w.WriteMsg(new(dns.Msg))
require.NoError(testutil.PanicT{}, err)
})
ups := (&url.URL{
goodUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, hdlr).String(),
Host: newLocalUpstreamListener(t, 0, goodHandler).String(),
}).String()
badUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
}).String()
goodAndBadUps := strings.Join([]string{goodUps, badUps}, " ")
const (
upsTimeout = 100 * time.Millisecond
@@ -494,7 +504,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
upstreamHost = "custom.localhost"
)
hostsListener := newLocalUpstreamListener(t, 0, hdlr)
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
hostsUps := (&url.URL{
Scheme: "tcp",
Host: netutil.JoinHostPort(upstreamHost, hostsListener.Port()),
@@ -527,7 +537,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
},
ServePlainDNS: true,
}, nil)
srv.etcHosts = upstream.NewHostsResolver(hc)
srv.etcHosts = hc
startDeferStop(t, srv)
testCases := []struct {
@@ -535,6 +545,43 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
wantResp map[string]any
name string
}{{
body: map[string]any{
"upstream_dns": []string{goodUps},
},
wantResp: map[string]any{
goodUps: "OK",
},
name: "success",
}, {
body: map[string]any{
"upstream_dns": []string{badUps},
},
wantResp: map[string]any{
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "broken",
}, {
body: map[string]any{
"upstream_dns": []string{goodUps, badUps},
},
wantResp: map[string]any{
goodUps: "OK",
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "both",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]" + badUps},
},
wantResp: map[string]any{
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
name: "domain_specific_error",
}, {
body: map[string]any{
"upstream_dns": []string{hostsUps},
},
@@ -544,12 +591,63 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
name: "etc_hosts",
}, {
body: map[string]any{
"upstream_dns": []string{ups, "#this.is.comment"},
"fallback_dns": []string{goodUps},
},
wantResp: map[string]any{
ups: "OK",
goodUps: "OK",
},
name: "comment_mix",
name: "fallback_success",
}, {
body: map[string]any{
"fallback_dns": []string{badUps},
},
wantResp: map[string]any{
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "fallback_broken",
}, {
body: map[string]any{
"fallback_dns": []string{goodUps, "#this.is.comment"},
},
wantResp: map[string]any{
goodUps: "OK",
},
name: "fallback_comment_mix",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]" + goodUps + " " + badUps},
},
wantResp: map[string]any{
goodUps: "OK",
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
name: "multiple_domain_specific_upstreams",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]/]1.2.3.4"},
},
wantResp: map[string]any{
"[/domain.example/]/]1.2.3.4": `wrong upstream format: ` +
`bad upstream for domain "[/domain.example/]/]1.2.3.4": ` +
`duplicated separator`,
},
name: "bad_specification",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]" + goodAndBadUps},
"fallback_dns": []string{"[/domain.example/]" + goodAndBadUps},
"private_upstream": []string{"[/domain.example/]" + goodAndBadUps},
},
wantResp: map[string]any{
goodUps: "OK",
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
name: "all_different",
}}
for _, tc := range testCases {

View File

@@ -191,7 +191,7 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
defer log.Debug("dnsforward: finished processing initial")
pctx := dctx.proxyCtx
s.processClientIP(pctx.Addr.Addr())
s.processClientIP(pctx.Addr)
q := pctx.Req.Question[0]
qt := q.Qtype
@@ -228,8 +228,9 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
}
// processClientIP sends the client IP address to s.addrProc, if needed.
func (s *Server) processClientIP(addr netip.Addr) {
if !addr.IsValid() {
func (s *Server) processClientIP(addr net.Addr) {
clientIP := netutil.NetAddrToAddrPort(addr).Addr()
if clientIP == (netip.Addr{}) {
log.Info("dnsforward: warning: bad client addr %q", addr)
return
@@ -240,7 +241,7 @@ func (s *Server) processClientIP(addr netip.Addr) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
s.addrProc.Process(addr)
s.addrProc.Process(clientIP)
}
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
@@ -350,7 +351,12 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
rc = resultCodeSuccess
dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr().AsSlice())
var ip net.IP
if ip, _ = netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr); ip == nil {
return rc
}
dctx.isLocalClient = s.privateNets.Contains(ip)
return rc
}
@@ -825,12 +831,12 @@ func (s *Server) dhcpHostFromRequest(q *dns.Question) (reqHost string) {
// setCustomUpstream sets custom upstream settings in pctx, if necessary.
func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
if !pctx.Addr.IsValid() || s.conf.ClientsContainer == nil {
if pctx.Addr == nil || s.conf.ClientsContainer == nil {
return
}
// Use the ClientID first, since it has a higher priority.
id := stringutil.Coalesce(clientID, pctx.Addr.Addr().String())
id := stringutil.Coalesce(clientID, ipStringFromAddr(pctx.Addr))
upsConf, err := s.conf.ClientsContainer.UpstreamConfigByID(id, s.bootstrap)
if err != nil {
log.Error("dnsforward: getting custom upstreams for client %s: %s", id, err)

View File

@@ -97,14 +97,14 @@ func TestServer_ProcessInitial(t *testing.T) {
dctx := &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: createTestMessageWithType(tc.target, tc.qType),
Addr: testClientAddrPort,
Addr: testClientAddr,
RequestID: 1234,
},
}
gotRC := s.processInitial(dctx)
assert.Equal(t, tc.wantRC, gotRC)
assert.Equal(t, testClientAddrPort.Addr(), gotAddr)
assert.Equal(t, netutil.NetAddrToAddrPort(testClientAddr).Addr(), gotAddr)
if tc.wantRCode > 0 {
gotResp := dctx.proxyCtx.Res
@@ -201,7 +201,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
Proto: proxy.ProtoUDP,
Req: tc.req,
Res: resp,
Addr: testClientAddrPort,
Addr: testClientAddr,
},
}
@@ -397,27 +397,33 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
}
testCases := []struct {
want assert.BoolAssertionFunc
name string
cliAddr netip.AddrPort
want assert.BoolAssertionFunc
name string
cliIP net.IP
}{{
want: assert.True,
name: "local",
cliAddr: netip.MustParseAddrPort("192.168.0.1:1"),
want: assert.True,
name: "local",
cliIP: net.IP{192, 168, 0, 1},
}, {
want: assert.False,
name: "external",
cliAddr: netip.MustParseAddrPort("250.249.0.1:1"),
want: assert.False,
name: "external",
cliIP: net.IP{250, 249, 0, 1},
}, {
want: assert.False,
name: "invalid",
cliAddr: netip.AddrPort{},
want: assert.False,
name: "invalid",
cliIP: net.IP{1, 2, 3, 4, 5},
}, {
want: assert.False,
name: "nil",
cliIP: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
proxyCtx := &proxy.DNSContext{
Addr: tc.cliAddr,
Addr: &net.TCPAddr{
IP: tc.cliIP,
},
}
dctx := &dnsContext{
proxyCtx: proxyCtx,
@@ -705,31 +711,31 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
name string
want string
question net.IP
cliAddr netip.AddrPort
cliIP net.IP
wantLen int
}{{
name: "from_local_to_external",
want: "host1.example.net.",
question: net.IP{254, 253, 252, 251},
cliAddr: netip.MustParseAddrPort("192.168.10.10:1"),
cliIP: net.IP{192, 168, 10, 10},
wantLen: 1,
}, {
name: "from_external_for_local",
want: "",
question: net.IP{192, 168, 1, 1},
cliAddr: netip.MustParseAddrPort("254.253.252.251:1"),
cliIP: net.IP{254, 253, 252, 251},
wantLen: 0,
}, {
name: "from_local_for_local",
want: "some.local-client.",
question: net.IP{192, 168, 1, 1},
cliAddr: netip.MustParseAddrPort("192.168.1.2:1"),
cliIP: net.IP{192, 168, 1, 2},
wantLen: 1,
}, {
name: "from_external_for_external",
want: "host1.example.net.",
question: net.IP{254, 253, 252, 251},
cliAddr: netip.MustParseAddrPort("254.253.252.255:1"),
cliIP: net.IP{254, 253, 252, 255},
wantLen: 1,
}}
@@ -741,7 +747,9 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
pctx := &proxy.DNSContext{
Proto: proxy.ProtoTCP,
Req: req,
Addr: tc.cliAddr,
Addr: &net.TCPAddr{
IP: tc.cliIP,
},
}
t.Run(tc.name, func(t *testing.T) {
@@ -786,7 +794,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
var dnsCtx *dnsContext
setup := func(use bool) {
proxyCtx = &proxy.DNSContext{
Addr: testClientAddrPort,
Addr: testClientAddr,
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
}
dnsCtx = &dnsContext{

View File

@@ -10,7 +10,9 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// Write Stats data and logs
@@ -23,12 +25,13 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
host := aghnet.NormalizeDomain(q.Name)
processingTime := time.Since(dctx.startTime)
ip := pctx.Addr.Addr().AsSlice()
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
ip = slices.Clone(ip)
s.anonymizer.Load()(ip)
log.Debug("dnsforward: client ip for stats and querylog: %s", ip)
ipStr := pctx.Addr.Addr().String()
ipStr := ip.String()
ids := []string{ipStr, dctx.clientID}
qt, cl := q.Qtype, q.Qclass

View File

@@ -1,7 +1,7 @@
package dnsforward
import (
"net/netip"
"net"
"testing"
"time"
@@ -65,7 +65,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name string
domain string
proto proxy.Proto
addr netip.AddrPort
addr net.Addr
clientID string
wantLogProto querylog.ClientProto
wantStatClient string
@@ -76,7 +76,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp",
domain: domain,
proto: proxy.ProtoUDP,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.4",
@@ -87,7 +87,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_tls_clientid",
domain: domain,
proto: proxy.ProtoTLS,
addr: testClientAddrPort,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "cli42",
wantLogProto: querylog.ClientProtoDoT,
wantStatClient: "cli42",
@@ -98,7 +98,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_tls",
domain: domain,
proto: proxy.ProtoTLS,
addr: testClientAddrPort,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: querylog.ClientProtoDoT,
wantStatClient: "1.2.3.4",
@@ -109,7 +109,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_quic",
domain: domain,
proto: proxy.ProtoQUIC,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: querylog.ClientProtoDoQ,
wantStatClient: "1.2.3.4",
@@ -120,7 +120,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_https",
domain: domain,
proto: proxy.ProtoHTTPS,
addr: testClientAddrPort,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: querylog.ClientProtoDoH,
wantStatClient: "1.2.3.4",
@@ -131,7 +131,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_dnscrypt",
domain: domain,
proto: proxy.ProtoDNSCrypt,
addr: testClientAddrPort,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: querylog.ClientProtoDNSCrypt,
wantStatClient: "1.2.3.4",
@@ -142,7 +142,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp_filtered",
domain: domain,
proto: proxy.ProtoUDP,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.4",
@@ -153,7 +153,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp_sb",
domain: domain,
proto: proxy.ProtoUDP,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.4",
@@ -164,7 +164,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp_ss",
domain: domain,
proto: proxy.ProtoUDP,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.4",
@@ -175,7 +175,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp_pc",
domain: domain,
proto: proxy.ProtoUDP,
addr: testClientAddrPort,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.4",
@@ -186,10 +186,10 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
name: "success_udp_pc_empty_fqdn",
domain: ".",
proto: proxy.ProtoUDP,
addr: netip.MustParseAddrPort("4.3.2.1:1234"),
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 5}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "4.3.2.1",
wantStatClient: "1.2.3.5",
wantCode: resultCodeSuccess,
reason: filtering.FilteredParental,
wantStatResult: stats.RParental,

View File

@@ -2,43 +2,14 @@ package dnsforward
import (
"fmt"
"net"
"net/netip"
"os"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
const (
// errNotDomainSpecific is returned when the upstream should be
// domain-specific, but isn't.
errNotDomainSpecific errors.Error = "not a domain-specific upstream"
// errMissingSeparator is returned when the domain-specific part of the
// upstream configuration line isn't closed.
errMissingSeparator errors.Error = "missing separator"
// errDupSeparator is returned when the domain-specific part of the upstream
// configuration line contains more than one ending separator.
errDupSeparator errors.Error = "duplicated separator"
// errNoDefaultUpstreams is returned when there are no default upstreams
// specified in the upstream configuration.
errNoDefaultUpstreams errors.Error = "no default upstreams specified"
// errWrongResponse is returned when the checked upstream replies in an
// unexpected way.
errWrongResponse errors.Error = "wrong response"
)
// loadUpstreams parses upstream DNS servers from the configured file or from
@@ -187,183 +158,3 @@ func (s *Server) createBootstrap(
return r, boots, nil
}
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
// This function is useful for filtering out non-upstream lines from upstream
// configs.
func IsCommentOrEmpty(s string) (ok bool) {
return len(s) == 0 || s[0] == '#'
}
// newUpstreamConfig validates upstreams and returns an appropriate upstream
// configuration or nil if it can't be built.
//
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
// slice already so that this function may be considered useless.
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
// No need to validate comments and empty lines.
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
if len(upstreams) == 0 {
// Consider this case valid since it means the default server should be
// used.
return nil, nil
}
err = validateUpstreamConfig(upstreams)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
conf, err = proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: net.DefaultResolver,
Timeout: DefaultTimeout,
},
)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
} else if len(conf.Upstreams) == 0 {
return nil, errNoDefaultUpstreams
}
return conf, nil
}
// validateUpstreamConfig validates each upstream from the upstream
// configuration and returns an error if any upstream is invalid.
//
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
func validateUpstreamConfig(conf []string) (err error) {
for _, u := range conf {
var ups []string
var isSpecific bool
ups, isSpecific, err = splitUpstreamLine(u)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
for _, addr := range ups {
_, err = validateUpstream(addr, isSpecific)
if err != nil {
return fmt.Errorf("validating upstream %q: %w", addr, err)
}
}
}
return nil
}
// ValidateUpstreams validates each upstream and returns an error if any
// upstream is invalid or if there are no default upstreams specified.
//
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
func ValidateUpstreams(upstreams []string) (err error) {
_, err = newUpstreamConfig(upstreams)
return err
}
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
// upstream is invalid or if there are no default upstreams specified. It also
// checks each domain of domain-specific upstreams for being ARPA pointing to
// a locally-served network. privateNets must not be nil.
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
conf, err := newUpstreamConfig(upstreams)
if err != nil {
return fmt.Errorf("creating config: %w", err)
}
if conf == nil {
return nil
}
keys := maps.Keys(conf.DomainReservedUpstreams)
slices.Sort(keys)
var errs []error
for _, domain := range keys {
var subnet netip.Prefix
subnet, err = extractARPASubnet(domain)
if err != nil {
errs = append(errs, err)
continue
}
if !privateNets.Contains(subnet.Addr().AsSlice()) {
errs = append(
errs,
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
)
}
}
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
}
// protocols are the supported URL schemes for upstreams.
var protocols = []string{"h3", "https", "quic", "sdns", "tcp", "tls", "udp"}
// validateUpstream returns an error if u alongside with domains is not a valid
// upstream configuration. useDefault is true if the upstream is
// domain-specific and is configured to point at the default upstream server
// which is validated separately. The upstream is considered domain-specific
// only if domains is at least not nil.
func validateUpstream(u string, isSpecific bool) (useDefault bool, err error) {
// The special server address '#' means that default server must be used.
if useDefault = u == "#" && isSpecific; useDefault {
return useDefault, nil
}
// Check if the upstream has a valid protocol prefix.
//
// TODO(e.burkov): Validate the domain name.
if proto, _, ok := strings.Cut(u, "://"); ok {
if !slices.Contains(protocols, proto) {
return false, fmt.Errorf("bad protocol %q", proto)
}
} else if _, err = netip.ParseAddr(u); err == nil {
return false, nil
} else if _, err = netip.ParseAddrPort(u); err == nil {
return false, nil
}
return false, err
}
// splitUpstreamLine returns the upstreams and the specified domains. domains
// is nil when the upstream is not domains-specific. Otherwise it may also be
// empty.
func splitUpstreamLine(upstreamStr string) (upstreams []string, isSpecific bool, err error) {
if !strings.HasPrefix(upstreamStr, "[/") {
return []string{upstreamStr}, false, nil
}
defer func() { err = errors.Annotate(err, "splitting upstream line %q: %w", upstreamStr) }()
doms, ups, found := strings.Cut(upstreamStr[2:], "/]")
if !found {
return nil, false, errMissingSeparator
} else if strings.Contains(ups, "/]") {
return nil, false, errDupSeparator
}
for i, host := range strings.Split(doms, "/") {
if host == "" {
continue
}
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
if err != nil {
return nil, false, fmt.Errorf("domain at index %d: %w", i, err)
}
isSpecific = true
}
return strings.Fields(ups), isSpecific, nil
}

View File

@@ -1,223 +0,0 @@
package dnsforward
import (
"net"
"net/url"
"strings"
"testing"
"time"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUpstreamConfigValidator(t *testing.T) {
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, err)
})
badHandler := dns.HandlerFunc(func(w dns.ResponseWriter, _ *dns.Msg) {
err := w.WriteMsg(new(dns.Msg))
require.NoError(testutil.PanicT{}, err)
})
goodUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, goodHandler).String(),
}).String()
badUps := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
}).String()
goodAndBadUps := strings.Join([]string{goodUps, badUps}, " ")
// upsTimeout restricts the checking process to prevent the test from
// hanging.
const upsTimeout = 100 * time.Millisecond
testCases := []struct {
want map[string]string
name string
general []string
fallback []string
private []string
}{{
name: "success",
general: []string{goodUps},
want: map[string]string{
goodUps: "OK",
},
}, {
name: "broken",
general: []string{badUps},
want: map[string]string{
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
}, {
name: "both",
general: []string{goodUps, badUps, goodUps},
want: map[string]string{
goodUps: "OK",
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
}, {
name: "domain_specific_error",
general: []string{"[/domain.example/]" + badUps},
want: map[string]string{
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
}, {
name: "fallback_success",
fallback: []string{goodUps},
want: map[string]string{
goodUps: "OK",
},
}, {
name: "fallback_broken",
fallback: []string{badUps},
want: map[string]string{
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
}, {
name: "multiple_domain_specific_upstreams",
general: []string{"[/domain.example/]" + goodAndBadUps},
want: map[string]string{
goodUps: "OK",
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
}, {
name: "bad_specification",
general: []string{"[/domain.example/]/]1.2.3.4"},
want: map[string]string{
"[/domain.example/]/]1.2.3.4": `splitting upstream line ` +
`"[/domain.example/]/]1.2.3.4": duplicated separator`,
},
}, {
name: "all_different",
general: []string{"[/domain.example/]" + goodAndBadUps},
fallback: []string{"[/domain.example/]" + goodAndBadUps},
private: []string{"[/domain.example/]" + goodAndBadUps},
want: map[string]string{
goodUps: "OK",
badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
}, {
name: "bad_specific_domains",
general: []string{"[/example/]/]" + goodUps},
fallback: []string{"[/example/" + goodUps},
private: []string{"[/example//bad.123/]" + goodUps},
want: map[string]string{
`[/example/]/]` + goodUps: `splitting upstream line ` +
`"[/example/]/]` + goodUps + `": duplicated separator`,
`[/example/` + goodUps: `splitting upstream line ` +
`"[/example/` + goodUps + `": missing separator`,
`[/example//bad.123/]` + goodUps: `splitting upstream line ` +
`"[/example//bad.123/]` + goodUps + `": domain at index 2: ` +
`bad domain name "bad.123": ` +
`bad top-level domain name label "123": all octets are numeric`,
},
}, {
name: "non-specific_default",
general: []string{
"#",
"[/example/]#",
},
want: map[string]string{
"#": "not a domain-specific upstream",
},
}, {
name: "bad_proto",
general: []string{
"bad://1.2.3.4",
},
want: map[string]string{
"bad://1.2.3.4": `bad protocol "bad"`,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cv := newUpstreamConfigValidator(tc.general, tc.fallback, tc.private, &upstream.Options{
Timeout: upsTimeout,
Bootstrap: net.DefaultResolver,
})
cv.check()
cv.close()
assert.Equal(t, tc.want, cv.status())
})
}
}
func TestUpstreamConfigValidator_Check_once(t *testing.T) {
type signal = struct{}
reqCh := make(chan signal)
hdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
pt := testutil.PanicT{}
err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(pt, err)
testutil.RequireSend(pt, reqCh, signal{}, testTimeout)
})
addr := (&url.URL{
Scheme: "tcp",
Host: newLocalUpstreamListener(t, 0, hdlr).String(),
}).String()
twoAddrs := strings.Join([]string{addr, addr}, " ")
wantStatus := map[string]string{
addr: "OK",
}
testCases := []struct {
name string
ups []string
}{{
name: "common",
ups: []string{addr, addr, addr},
}, {
name: "domain-specific",
ups: []string{"[/one.example/]" + addr, "[/two.example/]" + twoAddrs},
}, {
name: "both",
ups: []string{addr, "[/one.example/]" + addr, addr, "[/two.example/]" + twoAddrs},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cv := newUpstreamConfigValidator(tc.ups, nil, nil, &upstream.Options{
Timeout: testTimeout,
})
go func() {
cv.check()
testutil.RequireSend(testutil.PanicT{}, reqCh, signal{}, testTimeout)
}()
// Wait for the only request to be sent.
testutil.RequireReceive(t, reqCh, testTimeout)
// Wait for the check to finish.
testutil.RequireReceive(t, reqCh, testTimeout)
cv.close()
require.Equal(t, wantStatus, cv.status())
})
}
}

View File

@@ -1,6 +1,7 @@
package filtering
import (
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
@@ -94,3 +95,39 @@ func (d *DNSFilter) processDNSResultRewrites(
return res
}
// appendRewriteResultFromHost appends the rewrite result from rec to vals and
// resRules.
func appendRewriteResultFromHost(
vals []rules.RRValue,
resRules []*ResultRule,
rec *hostsfile.Record,
qtype uint16,
) (updatedVals []rules.RRValue, updatedRules []*ResultRule) {
switch qtype {
case dns.TypeA:
if !rec.Addr.Is4() {
return vals, resRules
}
vals = append(vals, rec.Addr)
case dns.TypeAAAA:
if !rec.Addr.Is6() {
return vals, resRules
}
vals = append(vals, rec.Addr)
case dns.TypePTR:
for _, name := range rec.Names {
vals = append(vals, name)
}
}
recText, _ := rec.MarshalText()
resRules = append(resRules, &ResultRule{
FilterListID: SysHostsListID,
Text: string(recText),
})
return vals, resRules
}

View File

@@ -220,19 +220,15 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
addrv4 := netip.MustParseAddr("1.2.3.4")
addrv6 := netip.MustParseAddr("::1")
addrMapped := netip.MustParseAddr("::ffff:1.2.3.4")
addrv4Dup := netip.MustParseAddr("4.3.2.1")
data := fmt.Sprintf(
""+
"%[1]s v4.host.example\n"+
"%[2]s v6.host.example\n"+
"%[3]s mapped.host.example\n"+
"%[4]s v4.host.with-dup\n"+
"%[4]s v4.host.with-dup\n",
"%s v4.host.example\n"+
"%s v6.host.example\n"+
"%s mapped.host.example\n",
addrv4,
addrv6,
addrMapped,
addrv4Dup,
)
files := fstest.MapFS{
@@ -347,15 +343,6 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeCNAME,
wantRules: nil,
wantResps: nil,
}, {
name: "v4_dup",
host: "v4.host.with-dup",
dtyp: dns.TypeA,
wantRules: []*ResultRule{{
Text: "4.3.2.1 v4.host.with-dup",
FilterListID: SysHostsListID,
}},
wantResps: []rules.RRValue{addrv4Dup},
}}
for _, tc := range testCases {

View File

@@ -18,6 +18,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
@@ -99,7 +100,7 @@ type Config struct {
// system configuration files (e.g. /etc/hosts).
//
// TODO(e.burkov): Move it to dnsforward entirely.
EtcHosts hostsfile.Storage `yaml:"-"`
EtcHosts *aghnet.HostsContainer `yaml:"-"`
// Called when the configuration is changed by HTTP request
ConfigModified func() `yaml:"-"`
@@ -481,6 +482,15 @@ func (d *DNSFilter) SetProtectionEnabled(status bool) {
d.conf.ProtectionEnabled = status
}
// EtcHostsRecords returns the hosts records for the hostname.
func (d *DNSFilter) EtcHostsRecords(hostname string) (recs []*hostsfile.Record) {
if d.conf.EtcHosts != nil {
return d.conf.EtcHosts.MatchName(hostname)
}
return recs
}
// SetBlockingMode sets blocking mode properties.
func (d *DNSFilter) SetBlockingMode(mode BlockingMode, bIPv4, bIPv6 netip.Addr) {
d.confMu.Lock()
@@ -627,10 +637,39 @@ func (d *DNSFilter) matchSysHosts(
) (res Result, err error) {
// TODO(e.burkov): Where else is this checked?
if !setts.FilteringEnabled || d.conf.EtcHosts == nil {
return Result{}, nil
return res, nil
}
var recs []*hostsfile.Record
switch qtype {
case dns.TypeA, dns.TypeAAAA:
recs = d.conf.EtcHosts.MatchName(host)
case dns.TypePTR:
var ip net.IP
ip, err = netutil.IPFromReversedAddr(host)
if err != nil {
log.Debug("filtering: failed to parse PTR record %q: %s", host, err)
return res, nil
}
addr, _ := netip.AddrFromSlice(ip)
recs = d.conf.EtcHosts.MatchAddr(addr)
default:
log.Debug("filtering: unsupported query type %s", dns.Type(qtype))
}
var vals []rules.RRValue
var resRules []*ResultRule
resRulesLen := 0
for _, rec := range recs {
vals, resRules = appendRewriteResultFromHost(vals, resRules, rec, qtype)
if len(resRules) > resRulesLen {
resRulesLen = len(resRules)
log.Debug("filtering: matched %s in %q", host, rec.Source)
}
}
vals, rs := hostsRewrites(qtype, host, d.conf.EtcHosts)
if len(vals) > 0 {
res.DNSRewriteResult = &DNSRewriteResult{
Response: DNSRewriteResultResponse{
@@ -638,64 +677,13 @@ func (d *DNSFilter) matchSysHosts(
},
RCode: dns.RcodeSuccess,
}
res.Rules = rs
res.Reason = RewrittenAutoHosts
res.Rules = resRules
res.Reason = RewrittenRule
}
return res, nil
}
// hostsRewrites returns values and rules matched by qt and host within hs.
func hostsRewrites(
qtype uint16,
host string,
hs hostsfile.Storage,
) (vals []rules.RRValue, rs []*ResultRule) {
var isValidProto func(netip.Addr) (ok bool)
switch qtype {
case dns.TypeA:
isValidProto = netip.Addr.Is4
case dns.TypeAAAA:
isValidProto = netip.Addr.Is6
case dns.TypePTR:
// TODO(e.burkov): Add some [netip]-aware alternative to [netutil].
ip, err := netutil.IPFromReversedAddr(host)
if err != nil {
log.Debug("filtering: failed to parse PTR record %q: %s", host, err)
return nil, nil
}
addr, _ := netip.AddrFromSlice(ip)
for _, name := range hs.ByAddr(addr) {
vals = append(vals, name)
rs = append(rs, &ResultRule{
Text: fmt.Sprintf("%s %s", addr, name),
FilterListID: SysHostsListID,
})
}
return vals, rs
default:
log.Debug("filtering: unsupported qtype %d", qtype)
return nil, nil
}
for _, addr := range hs.ByName(host) {
if isValidProto(addr) {
vals = append(vals, addr)
rs = append(rs, &ResultRule{
Text: fmt.Sprintf("%s %s", addr, host),
FilterListID: SysHostsListID,
})
}
}
return vals, rs
}
// processRewrites performs filtering based on the legacy rewrite records.
//
// Firstly, it finds CNAME rewrites for host. If the CNAME is the same as host,

View File

@@ -20,7 +20,6 @@ import (
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/maps"
@@ -140,9 +139,6 @@ func (clients *clientsContainer) Init(
return nil
}
// handleHostsUpdates receives the updates from the hosts container and adds
// them to the clients container. It's used to be called in a separate
// goroutine.
func (clients *clientsContainer) handleHostsUpdates() {
for upd := range clients.etcHosts.Upd() {
clients.addFromHostsFile(upd)
@@ -874,24 +870,21 @@ func (clients *clientsContainer) rmHostsBySrc(src client.Source) {
// addFromHostsFile fills the client-hostname pairing index from the system's
// hosts files.
func (clients *clientsContainer) addFromHostsFile(hosts *hostsfile.DefaultStorage) {
func (clients *clientsContainer) addFromHostsFile(hosts aghnet.Hosts) {
clients.lock.Lock()
defer clients.lock.Unlock()
clients.rmHostsBySrc(client.SourceHostsFile)
n := 0
hosts.RangeNames(func(addr netip.Addr, names []string) (cont bool) {
for addr, rec := range hosts {
// Only the first name of the first record is considered a canonical
// hostname for the IP address.
//
// TODO(e.burkov): Consider using all the names from all the records.
if clients.addHostLocked(addr, names[0], client.SourceHostsFile) {
n++
}
return true
})
clients.addHostLocked(addr, rec[0].Names[0], client.SourceHostsFile)
n++
}
log.Debug("clients: added %d client aliases from system hosts file", n)
}

View File

@@ -540,13 +540,11 @@ type safeSearchResolver struct{}
var _ filtering.Resolver = safeSearchResolver{}
// LookupIP implements [filtering.Resolver] interface for safeSearchResolver.
// It returns the slice of net.Addr with IPv4 and IPv6 instances.
func (r safeSearchResolver) LookupIP(
ctx context.Context,
network string,
host string,
) (ips []net.IP, err error) {
addrs, err := Context.dnsServer.Resolve(ctx, network, host)
// It returns the slice of net.IP with IPv4 and IPv6 instances.
//
// TODO(a.garipov): Support network.
func (r safeSearchResolver) LookupIP(_ context.Context, _, host string) (ips []net.IP, err error) {
addrs, err := Context.dnsServer.Resolve(host)
if err != nil {
return nil, err
}
@@ -556,7 +554,7 @@ func (r safeSearchResolver) LookupIP(
}
for _, a := range addrs {
ips = append(ips, a.AsSlice())
ips = append(ips, a.IP)
}
return ips, nil

View File

@@ -35,10 +35,8 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
)
@@ -233,12 +231,11 @@ func setupHostsContainer() (err error) {
return fmt.Errorf("initing hosts watcher: %w", err)
}
paths, err := hostsfile.DefaultHostsPaths()
if err != nil {
return fmt.Errorf("getting default system hosts paths: %w", err)
}
Context.etcHosts, err = aghnet.NewHostsContainer(osutil.RootDirFS(), hostsWatcher, paths...)
Context.etcHosts, err = aghnet.NewHostsContainer(
aghos.RootDirFS(),
hostsWatcher,
aghnet.DefaultHostsPaths()...,
)
if err != nil {
closeErr := hostsWatcher.Close()
if errors.Is(err, aghnet.ErrNoHostsPaths) {

View File

@@ -101,7 +101,6 @@ func (qc *queryConn) listAll() (sets []props, err error) {
type ipsetConn interface {
Add(name string, entries ...*ipset.Entry) (err error)
Close() (err error)
Header(name string) (p *ipset.HeaderPolicy, err error)
listAll() (sets []props, err error)
}
@@ -113,9 +112,6 @@ type props struct {
// name of the ipset.
name string
// typeName of the ipset.
typeName string
// family of the IP addresses in the ipset.
family netfilter.ProtoFamily
@@ -152,8 +148,6 @@ func (p *props) parseAttribute(a netfilter.Attribute) {
case ipset.AttrSetName:
// Trim the null character.
p.name = string(bytes.Trim(a.Data, "\x00"))
case ipset.AttrTypeName:
p.typeName = string(bytes.Trim(a.Data, "\x00"))
case ipset.AttrFamily:
p.family = netfilter.ProtoFamily(a.Data[0])
default:
@@ -269,9 +263,8 @@ func (m *manager) parseIpsetConfig(ipsetConf []string) (err error) {
return err
}
currentlyKnown := map[string]props{}
for _, p := range all {
currentlyKnown[p.name] = p
m.nameToIpset[p.name] = p
}
for i, confStr := range ipsetConf {
@@ -282,7 +275,7 @@ func (m *manager) parseIpsetConfig(ipsetConf []string) (err error) {
}
var ipsets []props
ipsets, err = m.ipsets(ipsetNames, currentlyKnown)
ipsets, err = m.ipsets(ipsetNames)
if err != nil {
return fmt.Errorf("getting ipsets from config line at idx %d: %w", i, err)
}
@@ -295,64 +288,18 @@ func (m *manager) parseIpsetConfig(ipsetConf []string) (err error) {
return nil
}
// ipsetProps returns the properties of an ipset with the given name.
//
// Additional header data query. See https://github.com/AdguardTeam/AdGuardHome/issues/6420.
//
// TODO(s.chzhen): Use *props.
func (m *manager) ipsetProps(name string) (p props, err error) {
// The family doesn't seem to matter when we use a header query, so
// query only the IPv4 one.
//
// TODO(a.garipov): Find out if this is a bug or a feature.
var res *ipset.HeaderPolicy
res, err = m.ipv4Conn.Header(name)
if err != nil {
return props{}, err
}
if res == nil || res.Family == nil {
return props{}, errors.Error("empty response or no family data")
}
family := netfilter.ProtoFamily(res.Family.Value)
if family != netfilter.ProtoIPv4 && family != netfilter.ProtoIPv6 {
return props{}, fmt.Errorf("unexpected ipset family %q", family)
}
typeName := res.TypeName.Get()
return props{
name: name,
typeName: typeName,
family: family,
isPersistent: false,
}, nil
}
// ipsets returns ipset properties of currently known ipsets. It also makes an
// additional ipset header data query if needed.
func (m *manager) ipsets(names []string, currentlyKnown map[string]props) (sets []props, err error) {
// ipsets returns currently known ipsets.
func (m *manager) ipsets(names []string) (sets []props, err error) {
for _, n := range names {
p, ok := currentlyKnown[n]
p, ok := m.nameToIpset[n]
if !ok {
return nil, fmt.Errorf("unknown ipset %q", n)
}
if p.family != netfilter.ProtoIPv4 && p.family != netfilter.ProtoIPv6 {
log.Debug("ipset: getting properties: %q %q unexpected ipset family %q",
p.name,
p.typeName,
p.family,
)
p, err = m.ipsetProps(n)
if err != nil {
return nil, fmt.Errorf("%q %q making header query: %w", p.name, p.typeName, err)
}
return nil, fmt.Errorf("%q unexpected ipset family %q", p.name, p.family)
}
m.nameToIpset[n] = p
sets = append(sets, p)
}
@@ -393,8 +340,6 @@ func newManagerWithDialer(ipsetConf []string, dial dialer) (mgr Manager, err err
return nil, fmt.Errorf("getting ipsets: %w", err)
}
log.Debug("ipset: initialized")
return m, nil
}
@@ -463,7 +408,7 @@ func (m *manager) addIPs(host string, set props, ips []net.IP) (n int, err error
err = conn.Add(set.name, entries...)
if err != nil {
return 0, fmt.Errorf("adding %q%s to %q %q: %w", host, ips, set.name, set.typeName, err)
return 0, fmt.Errorf("adding %q%s to ipset %q: %w", host, ips, set.name, err)
}
// Only add these to the cache once we're sure that all of them were
@@ -499,10 +444,10 @@ func (m *manager) addToSets(
return n, err
}
default:
return n, fmt.Errorf("%q %q unexpected family %q", set.name, set.typeName, set.family)
return n, fmt.Errorf("unexpected family %s for ipset %q", set.family, set.name)
}
log.Debug("ipset: added %d ips to set %q %q", nn, set.name, set.typeName)
log.Debug("ipset: added %d ips to set %s", nn, set.name)
n += nn
}

View File

@@ -47,11 +47,6 @@ func (c *fakeConn) Close() (err error) {
return nil
}
// Header implements the [ipsetConn] interface for *fakeConn.
func (c *fakeConn) Header(_ string) (_ *ipset.HeaderPolicy, _ error) {
return nil, nil
}
// listAll implements the [ipsetConn] interface for *fakeConn.
func (c *fakeConn) listAll() (sets []props, err error) {
return c.sets, nil

View File

@@ -339,6 +339,9 @@ func (r dayRange) toDayConfigJSON() (j *dayConfigJSON) {
// weeklyConfigJSON is the JSON configuration structure of Weekly.
type weeklyConfigJSON struct {
// TimeZone is the local time zone.
TimeZone string `json:"time_zone"`
// Days of the week.
Sunday *dayConfigJSON `json:"sun,omitempty"`
@@ -348,9 +351,6 @@ type weeklyConfigJSON struct {
Thursday *dayConfigJSON `json:"thu,omitempty"`
Friday *dayConfigJSON `json:"fri,omitempty"`
Saturday *dayConfigJSON `json:"sat,omitempty"`
// TimeZone is the local time zone.
TimeZone string `json:"time_zone"`
}
// dayConfigJSON is the JSON configuration structure of dayRange.

View File

@@ -163,10 +163,10 @@ yaml: "bad"
}
testCases := []struct {
want *Weekly
name string
wantErrMsg string
data []byte
want *Weekly
}{{
name: "empty",
wantErrMsg: "",
@@ -228,9 +228,9 @@ func TestWeekly_MarshalYAML(t *testing.T) {
}
testCases := []struct {
want *Weekly
name string
data []byte
want *Weekly
}{{
name: "empty",
data: []byte(""),
@@ -263,8 +263,8 @@ func TestWeekly_MarshalYAML(t *testing.T) {
func TestWeekly_Validate(t *testing.T) {
testCases := []struct {
name string
wantErrMsg string
in dayRange
wantErrMsg string
}{{
name: "empty",
wantErrMsg: "",
@@ -298,8 +298,8 @@ func TestWeekly_Validate(t *testing.T) {
func TestDayRange_Validate(t *testing.T) {
testCases := []struct {
name string
wantErrMsg string
in dayRange
wantErrMsg string
}{{
name: "empty",
wantErrMsg: "",
@@ -413,10 +413,10 @@ func TestWeekly_UnmarshalJSON(t *testing.T) {
}
testCases := []struct {
want *Weekly
name string
wantErrMsg string
data []byte
want *Weekly
}{{
name: "empty",
wantErrMsg: "unexpected end of JSON input",
@@ -478,9 +478,9 @@ func TestWeekly_MarshalJSON(t *testing.T) {
}
testCases := []struct {
want *Weekly
name string
data []byte
want *Weekly
}{{
name: "empty",
data: []byte(""),

View File

@@ -10,7 +10,7 @@ require (
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.18.2
github.com/uudashr/gocognit v1.1.2
golang.org/x/tools v0.16.0
golang.org/x/tools v0.15.0
golang.org/x/vuln v1.0.1
honnef.co/go/tools v0.4.6
mvdan.cc/gofumpt v0.5.0
@@ -26,9 +26,9 @@ require (
github.com/kyoh86/nolint v0.0.1 // 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/typeparams v0.0.0-20231206192017-f3f8817b8deb // indirect
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -49,8 +49,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/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/typeparams v0.0.0-20231206192017-f3f8817b8deb h1:O9ulz4QbYejvlkJzZ6gNIYs+YhXXWg886sWk5ugFPSw=
golang.org/x/exp/typeparams v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa h1:wJBD77KpXKOckDJT0rqU5EwZDmxcmTh6aXVpU6s6GBg=
golang.org/x/exp/typeparams v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@@ -63,7 +63,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -79,8 +79,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -93,8 +93,8 @@ golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -35,7 +35,7 @@ set -f -u
go_version="$( "${GO:-go}" version )"
readonly go_version
go_min_version='go1.20.12'
go_min_version='go1.20.11'
go_version_msg="
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
if you have the version installed, please set the GO environment variable.
@@ -206,6 +206,7 @@ run_linter gocognit --over='11'\
run_linter gocognit --over='10'\
./internal/aghalg/\
./internal/aghchan/\
./internal/aghhttp/\
./internal/aghrenameio/\
./internal/aghtest/\
@@ -217,7 +218,6 @@ run_linter gocognit --over='10'\
./internal/filtering/hashprefix/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
./internal/ipset\
./internal/next/\
./internal/rdns/\
./internal/schedule/\
@@ -243,6 +243,7 @@ run_linter nilness ./...
# TODO(a.garipov): Enable for all.
run_linter fieldalignment \
./internal/aghalg/\
./internal/aghchan/\
./internal/aghhttp/\
./internal/aghos/\
./internal/aghrenameio/\
@@ -256,11 +257,9 @@ run_linter fieldalignment \
./internal/filtering/rewrite/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
./internal/ipset/\
./internal/next/...\
./internal/querylog/\
./internal/rdns/\
./internal/schedule/\
./internal/stats/\
./internal/updater/\
./internal/version/\
@@ -288,7 +287,6 @@ run_linter gosec --quiet\
./internal/filtering/rewrite/\
./internal/filtering/rulelist/\
./internal/filtering/safesearch/\
./internal/ipset/\
./internal/next/\
./internal/rdns/\
./internal/schedule/\